Spring Container는 스프링에서 자바 객체를 관리하는 공간을 말한다.
- 스프링에선 자바 객체를 빈(Bean) 이라고 한다.
- 즉 스프링 컨테이너가 개발자 대신해 빈의 생성부터 소멸까지, Bean의 수명 주기를 관리하는 공간이라고 할 수 있다.
- Container는
Spring Context
, 또는IoC Container
라고 불리기도 한다. (Spring Container == Spring Context == Spring IoC Container)
- IoC(Inversion of Control) : 제어의 역전
Spring Container는 크게 두 종류로 나눌 수 있다.
❶ BeanFactory
❷ ApplicationContext
.getBean()
메서드 등 다양한 Bean 관련 메소드를 제공한다.new AnnotationConfigApplicationContext(AppConfig.class)
→ ApplicationContext
인터페이스의 구현체💡 ApplicationContext가 제공하는 부가 기능 (인터페이스)
- 메시지 소스를 활용한 국제화기능 (MessageSource)
- 환경변수 (EnvironmentCapable)
- 애플레케이션 이벤트 (ApplicationEventPublisher)
- 편리한 리소스 조회(ResourceLoder)
class pojo {
private String text;
private int number;
public String toString() {
return text + ":" + number;
}
}
no-arg
를 기본으로 한다.class JavaBean {
private int number;
//1. no-arg 생성자 (자동 설정으로 생략 가능)
public JavaBean() {
}
}
class JavaBean {
private int number;
//1. no-arg 생성자 (자동 설정으로 생략 가능)
public JavaBean() {
}
//2. getters and setters
public int getNumber() {
return number;
}
public int setNumber(int number) {
this.number = number;
}
}
class JavaBean implements Serializable {
private int number;
//1. no-arg 생성자 (자동 설정으로 생략 가능)
public JavaBean() {
}
//2. getters and setters
public int getNumber() {
return number;
}
public int setNumber(int number) {
this.number = number;
}
}
빈(Bean) : 스프링 컨테이너에 의해 관리되는 재사용 가능한 소프트웨어 컴포넌트이다.
new 키워드
대신 사용한다.❓ 스프링 빈(Bean)을 사용하는 이유
- 가장 큰 이유는 스프링 간 객체가 의존관계를 관리하도록 하는 것에 가장 큰 목적이 있다.
- 객체가 의존관계를 등록할 때 스프링 컨테이너에서 해당하는 Bean을 찾고, 그 Bean과 의존성을 만든다.
대표적인 Spring Bean을 등록하는 방법으론 3가지가 있다.
- xml에 직접 등록
@Bean
어노테이션을 이용@Component
,@Controller
,@Service
,@Repository
어노테이션을 이용
1. XML에 직접 등록하는 방법
<bean id="helloService" class="com.example.myapp.di.HelloService"/>
<bean id="helloController" class="com.example.myapp.di.HelloController" p:helloService-ref="helloService">
</bean>
bean
태그를 사용한다.2. @Bean 어노테이션을 이용
인스턴스를 반환하고 Bean에 등록하는 코드
package com.example.myapp.di;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ImportResource;
@Configurable
@ComponentScan(basePackages= {"com.example.myapp"})
@ImportResource(value= {"classpath:application-config.xml"})
public class AppConfig {
@Bean
public IHelloService helloService() {
return new HelloService();
}
@Bean
public HelloController helloController() {
HelloController controller = new HelloController();
controller.setHelloService(helloService());
return controller;
}
}
@Bean
를 사용하고 AppConfig 객체 위에 @Configurable
, @ComponentScan
, @ImportResource
어노테이션을 선언해준다. 이때 어노테이션을 사용하기 위해선 꼭 application-xml에서 context를 추가해주어야 한다!
package com.example.myapp.di;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;
public class HelloMain {
public static void main(String[] args) {
AbstractApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
System.out.println("-----------------------");
HelloController controller = context.getBean("helloController", HelloController.class);
controller.hello("홍길동");
System.out.println("=====================");
context.close();
}
}
3. @Component, @Controller, @Service, @Repository 어노테이션을 이용
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">
<context:component-scan base-package="com.example.myapp.hr"/>
</beans>
application-config.xml 파일
package com.example.myapp.hr;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
@Controller
public class EmpController {
private IEmpService empService;
@Autowired
public EmpController(IEmpService empService) {
this.empService = empService;
}
void printInfo() {
int count = empService.getEmpCount(50);
System.out.println("사원의 수 : " + count);
}
}
👆 @Controller
와 @Autowired
를 이용해 의존성 주입까지 한 코드이다.
주석은 개발자가 좀 더 직관적으로 쉽게 코드를 이해할 수 있게 해주며, 다른 사람에게 설명할 수 있도록 정보를 제공하는 역할을 한다.
Annotation(어노테이션)
또한 사전적 의미로는 주석을 의미하긴 하지만, 자바에서 Annotation(@)
은 코드와 코드 사이에 특별한 의미, 기능을 수행하도록 하는 역할을 한다. 프로그램 코드의 일부가 아닌 프로그램에 관한 데이터를 제공하고, 코드에 정보를 추가하는 정형화된 방법이다.
주석과 비교해보자면, 주석은 사람에게 정보를 제공한다면 어노테이션은 특정 코드를 사용하는 프로그램에게 정보를 전달한다.
💡 어노테이션은
@
을 사용해 작성하며, 해당 타겟에 대한 동작을 수행하는 프로그램 외에는 다름 프로그램에게 영향을 주지 않는다.
어노테이션을 사용하기 위한 순서는 아래와 같다.
1. 어노테이션을 정의한다.
2. 클래스에 어노테이션을 배치한다.
3. 코드가 실행되는 중에 Reflection을 이용해 추가 정보를 획득하고 기능을 실시한다.
어노테이션은 크게 세 가지로 구분된다.
자바에서 기본적으로 제공되는 표준 어노테이션과 어노테이션을 정의하는데 사용되는 메타 어노테이션, 마지막으로 사용자 어노테이션이 있다.
SpringBoot를 자동으로 실행시켜주는 어노이테이션으로, Bean 등록은 두 단계로 진행된다.
1. @ComponentScan
을 통해 Component들을 Bean으로 등록한다.
2. @EnableAutoConfiguration
을 통해 미리 정의해둔 자바 설정 파일들을 Bean으로 등록한다.
@ComponentScan
선언에 의해 특정 패키지 안의 클래스들을 자동으로 스캔하여 @Component
어노테이션이 있는 클래스들에 대해 Bean 인스턴스를 생성한다.@Component
public class Student {
public Stdudent() {
System.out.println("hello");
}
}
@Component(value="mystudent")
public class Student {
public Stdudent() {
System.out.println("hello");
}
}
@Bean
은 개발자가 직접 제어 불가능한 외부 라이브러리 등을 Bean으로 만들려할 때 사용한다.
@Configuration
public class ApplicationConfig {
@Bean
public ArrayList<String> array() {
return new ArrayList<String>();
}
}
@Bean
을 사용하면 된다.@Bean
에 아무런 값을 지정하지 않았으므로 Bean id 값으로 → Method 이름을 CamelCase로 변경한 문자열로 등록된다.arrayList
가 Bean id가 된다.@Configuration
public class ApplicationConfig {
@Bean(name="myarray")
public ArrayList<String> array() {
return new ArrayList<String>();
}
}
name
값을 이용하면 원하는 id로 Bean을 등록할 수 있다.@Autowired
어노테이션을 작성하지 않아도 자동으로 와이어링된다. 👈 생성자 주입의 가장 큰 장점!@AllArgsConstructor
를 사용하기 때문이다.class BurgerChef {
private BurgerRecipe burgerRecipe;
public BurgerChef(BurgerRecipe burgerRecipe) {
this.burgerRecipe = burgerRecipe;
}
}
class BurgerRestaurantOwner {
private BurgerChef burgerChef = new BurgerChef(new HamburgerRecipe());
public void changeMenu() {
burgerChef = new BurgerChef(new CheeseBurgerRecipe());
}
}
class BurgerChef {
private BurgerRecipe burgerRecipe = new HamburgerRecipe();
public void setBurgerRecipe(BurgerRecipe burgerRecipe) {
this.burgerRecipe = burgerRecipe;
}
}
@Autowired
명시💡
@Bean
Vs@Component
차이는 ?👉
Bean
은 외부 라이브러리가 제공 하는 객체를 사용할 때 활용하고,Component
는 내부에서 직접 접근 가능 한 클래스를 사용할 때 활용한다.
Heading @Component
@Bean
Where? 모든 자바클래스에 사용 특정 메소드 위에만 사용(보통 스프링 설정 클래스에 이용) Eese of use Super Easy(코드 앞에 어노테이션 추가) 조금 복잡(Bean 생성을 위해 코드 전체 작성 필요) Autowiring 생성자/수정자(setter)/필드 주입 방식 중 선택 가능 자동 와이어링을 위해선 특정 메서드 호출 필요 Who Creates beans? 스프링 프레임워크가 생성 개발자가 생성(Bean 생성을 위한 코드 작성) Recommended For 대부분의 경우 외부라이브러리가 제공하거나 빈 생성 전에 자체적인 작업 로직이 많이 사용되는 경우 👉 이 외에도 선언 위치나 자세한 사용법의 차이는 해당 블로그를 참조하자.
스프링 IoC 컨테이너에게 해당 클래스가 Bean 구성 Class임을 알려주는 어노테이션이다.
@Bean
을 해당 클래스의 메소드에 적용하면 @AutoWired
로 Bean을 부를 수 있다.
@Component
, @Service
, @Repository
, @Controller
, @Configuration
이 붙은 빈들을 찾아서 Context에 Bean을 등록해 주는 어노테이션이다.
@Component
어노테이션이 있는 클래스에 대해 Bean 인스턴스를 생성한다.
@Component
어노테이션을 다 붙이면 되는거 아닌가?Spring에서 @Component
로 다 쓰지 않고 @Repository
, @Controller
등을 사용하는 이유는 무엇일까?
예를 들어 @Repository
는 DAO의 메소드에서 발생할 수 있는 unchecked exception들을 스프링의 DataAccessException으로 처리할 수 있기 때문이다.
+ 또한 가독성면에서도 해당 어노테이션이 갖는 클래스가 무엇을 하는지 단 번에 알 수 있다는 장점이 있다.
➕ Add !
@Named
:@Component
의 대안@Injects
:@Autowired
의 대안
Spring Boot에서 어노테이션을 통해 자동으로 Bean을 컨테이너에 설정하는 경우, 같은 인터페이스의 구현체 클래스 두 개 이상이 Bean으로 등록되면 아래와 같은 오류가 발생한다.
이럴 경우 Spring Boot가 어떤 Bean을 주입해야 하는지 알려주어야 하는데(의존성 주입), 그 방법으로는 크게 3가지가 있다.
❶
@Autowired
된 field의 이름을 Bean 이름으로 설정하는 방법
❷@Primary
어노테이션을 사용하는 방법
❸@Qualifier
어노테이션을 사용하는 방법
public interface Animal {
String sound();
}
@Component
public class Dog implements Animal {
public String sound() {
return "왈왈";
}
}
@Component
public class Cat implements Animal {
public String sound() {
return "야옹";
}
}
@Service
public class AnimalService {
private final Animal animal;
@Autowired
public AnimalService(Animal animal) {
this.animal = animal;
}
}
필드, setter 메서드, 생성자에 사용하며, Type에 따라 알아서 Bean을 주입해주는 역할을 한다. 즉 @Autowired
사용을 통해 자동으로 객체에 대한 의존성을 주입시킬 수 있다.
Controller 클래스에서 DAO나 Service에 과한 객체들을 주입시킬 때 많이 사용한다.
❗️스프링에서는
@Autowired
를 사용해 의존성을 주입한다.
이처럼 스프링에서는 @Autowired
이 Type을 확인한 후 알아서 Bean을 주입해주는 역할을 하고 있다. 그런데 만약 동일한 타입을 가진 Bean 객체가 두 개나 있다면 어떻게 될까?
@Autowired
의 주입 대상이 한 개여야 하는데, 만약 두 개 이상의 빈이 존재한다면, 어떤 빈을 선택해 주입해야 할지 알 수 없기 때문이다.@Autowired
가 적용된 필드나 설정 메서드의 proterty의 이름과 같은 이름을 가진 빈 객체가 존재할 경우엔 → 이름과 같은 빈 객체를 주입 받는다.가장 간단한 방법으로는 같은 타입의 여러 Bean이 있을 때 기본적으로 선택될 Bean에 @Primary
어노테이션을 붙여주면 자동적으로 해당 빈이 선택된다.
주입받을 때보다 모든 코드에 모든 코드에 @Qualifier
어노테이션을 붙여주는 것보다 훨씬 간단한 방법이다.
@Component
@Primary
public class Dog implements Animal {
public String sound() {
return "왈왈";
}
}
@Component
public class Cat implements Animal {
public String sound() {
return "야옹";
}
}
@Service
public class AnimalService {
private final Animal animal;
@Autowired
public AnimalService(Animal animal) {
this.animal = animal;
}
}
@Qualifier
어노테이션은 Bean에 추가 구분자를 붙여주는 방법으로, 생성자에서 해당 구분자를 명시하면 그 구분자(한정자)를 가진 Bean을 주입해준다.
⛔️ Caution!
이때
@Qualifier
어노테이션을 통해 Bean의 이름을 변경하는 것이 아니라, Bean 이름은 변경되지 않고, 추가적인 구분자가 생기는 것이다.
@Autowired
어노테이션이 주입된 대상에 @Qualifier
어노테이션을 설정한다.@Qualifier
의 값으로 앞서 설정한 구분자(한정자)를 사용한다.@Component
@Qualifier("dogdog")
public class Dog implements Animal {
public String sound() {
return "왈왈";
}
}
@Component
@Qualifier("catcat")
public class Cat implements Animal {
public String sound() {
return "야옹";
}
}
@Service
public class AnimalService {
private final Animal animal;
@Autowired
public AnimalService(@Qualifier("dogdog") Animal animal) {
this.animal = animal;
}
}
@Qualifier
어노테이션을 사용할 순 있지만, 코드의 명확성아 떨어져 유지보수가 어려워질 수 있으니 Bean 이름과 @Qualifier
는 별도로 관리하는 것이 좋다.@Qualifier
에 구분자를 지정하지 않는다면, Bean id를 기반으로 camelCase로 변환된 name을 사용한다.❗️
@Qualifier
사용 시 주의할 점
@Qualifier
에 지정한 한정자 값을 갖는 bean 객체가 존재하지 않는다면 Exception이 발생한다.
- (하지만 사실상 이 예외는 발생하기 힘들다. bean 객체를 만들지도 않았는데 Qualifier 처리를 할리 없으니까.)- 결과 :
@Qualifier
에 해당하는 빈 객체를 찾지 못하기 때문에, 스프링 컨테이너를 생성하는데 실패한다.
@Qualifier
가 명시되어 있는 경우, 같은 값을 갖는 bean객체여야 한다.@Qualifier
가 없는 경우@Qualifier
가 있는 경우@Qualifier
로 지정한 Bean 객체를 사용한다.👉
@Primary
과@Qualifier
둘 다 존재할 땐@Qualifier
이 우선이다.
- 스프링은 기본적으로 자동보다 수동으로 지정한 것에 높은 우선 순위를 부여한다.
- 따라서 자동적으로 Bean을 선택해주는
@Primary
보다 명시적으로 지정하는@Qualifier
이 더 높은 우선 순위를 가진다.
@Primary
는 같은 타입의 multiple beans이 존재할 때 → 이들의 선호도를 정의하여 하나의 구현체를 Default 값으로 사용한다. 따라서 기본값으로 주입되어야 하는 Bean을 특정하고 싶을 때 유용하다.
반면 다른 Bean을 같은 injection point에서 사용해야 하는 순간도 있을 것이다. 이 경우엔 @Qualifier
를 사용해 default로 주입된 bean이 아닌, 다른 틀정 Bean을 주입하도록 지정할 수 있다.
WHY? 👉 기본적으로 @Primary
는 default값을 정의하는 반면, @Qualifier
는 specific한 값을 정의하기 때문이다.
만약 메인 데이터베이스와 서브 데이터베이스가 있다 가정한다면,
@Primary
를 사용해 default 로 설정하고,@Qualifier
로 특정go 명시적으로 의존성을 주입받아 사용할 수 있다.Spring Bean의 기본 초기화는 이른 초기화방식이다.
@Component
나 @Bean
@Component
class ClassA {
}
@Component
@Lazy
class ClassB {
private ClassA classA;
public ClassB(ClassA classA ){
this.classA = class.A;
}
}
💡 이른 초기화 vs 지연 초기화 무엇이 좋을까
결론부터 얘기하면, 이른 초기화를 추천한다.
👉 스프링 설정에 오류가 발생한 경우, 이른 초기화를 사용하면 애플리케이션 시작 시 즉시 발견된다.
Heading 이른 초기화 지연 초기화 Bean 초기화 시점 애플리케이션 또는 스프링 컨텍스트의 시작 시 어플리케이션에서 처음 사용될 때 Default default Not Default 사용 @Lazy(value=false) 또는 (Absence of @Lazy) @Lazy(value=true) 초기화 오류 발생 시 오류로 인해 어플리케이션이 시작하지 않음 런타임 예외 Usage 자주 매우 드믐 메모리 소모 모든 bean이 시작 시 초기화되므로, 시작 시 바로 로딩됨 빈이 초기화되기 전에는 소모 메모리가 상대적으로 적음 추천 상황 거의 모든 Bean에 권장 해당 bean이 애플리케이션에서 매우 드물게 사용되고, bean이 사용되는 특정 시점에 로딩하고 싶은 경우
일반적으로 @Component
, @Service
, @Repository
등으로 자동 스캐닝한 Java Bean은 싱글톤 형태로 하나만 생성하는데, 이를 변경하기 위해 @Scope
어노테이션을 사용한다.
즉 @Scope
어노테이션을 사용해 Bean의 유효 범위를 설정한다.
🗣️ 싱글톤 패턴
- 클래스의 인스턴스가 오직 하나만 생성되는 것을 보장하는 디자인 패턴
- Spring Container는 별도로 싱글톤 패턴을 적용하지 않아도, 객체 인스턴스를 싱글톤으로 관리한다.
- 즉 Bean(객체)를 스프링 컨테이너에 등록하고 bean 조회 요청 시 → 새로 생성하지 않고 스프링 컨테이너에서 bean을 찾아 반환한다.
❗️ Important! - 싱글톤 유효 범위와 프로토타입 유효 범위
- 스프링 IoC 컨테이너에 대해 동일한 인스턴스를 다시 사용(재사용)하고 싶다면 👉 싱글톤 유효 범위
- 매번 새로운 인스턴스를 만들어 사용하고 싶다면 👉 프로토타입 유효 범위
@Component @Scope("유효 범위") // 또는 @Scope(value=유효 범위) class Ojc { ... }
- singleton : IoC 컨테이너 당 하나의 bean 인스턴스 생성
- prototype : 요구가 있을 때마다 새로운 bean 인스턴스 생성
- 즉 많은 객체 인스턴스가 스프링 IoC 컨테이너마다 생성될 수 있다.
- request : HTTP request 객체 당 하나의 bean 인스턴스 생성
- session : HTTP session 당 하나의 bean을 리턴
- globalSession : 전체 모든 세션에 대해 하나의 bean 인스턴스 생성
- Application : 전체 웹 애플리케이션 당 하나의 bean 인스턴스 생성
- Websocket : 웹 소켓 인스턴스 당 하나의 bean 인스턴스 생성
Java 싱글톤과 Spring 싱글톤은 99% 일치
하지만, 완전히 동일하다고 할 수는 없다.
⌙ 자바 가상 머신에서 여러 개의 스프링 IoC 컨테이너를 실행한다면 Java 싱글톤과 Spring 싱글톤은 다르게 된다.
⌙ 하지만 일반적으로 위와 같이 실행하진 않기 때문에 99% 동일
하다고 한다.
스프링은 자동 와이어링이 되자마자, @PostConstruct
으로 어노테이션된 메소드를 호출한다.
@PostConstruct
어노테이션은 의존성 주입이 완료되어 초기화가 수행된 후 실행되어야 하는 메소드에서 사용된다.@PostConstruct
public void initialize() {
someDependency.getReady();
}
@PreDestroy
는 애플리케이션이 종료되기 전, Bean이 컨텍스트에서 제거되기 전에 사용한다.
@PreDestroy
로 어노테이션된 메소드는 일반적으로 보유하고 있던 리소스를 해체하는 데 사용된다.@PreDestroy
public void cleanup() {
System.out.println("Cleanup");
}
Bean 등록과 설정 모두 XML에 작성할 경우 아래 포맷을 사용한다.
bean
태그 안에 스프링 bean 설정을 작성한다.<?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">
</beans>
Bean 등록은 XML, Bean 설정은 어노테이션으로 할 경우 아래 포맷을 사용한다.
context
네임 스페이스와 <context:annotaion-cofig />
코드를 추가하면 Bean 클래스의 어노테이션을 통해 Bean 설정을 할 수 있다.<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" 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 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
</beans>
Heading | XML Configuration | Annotations |
---|---|---|
사용 편의성 | 매우 쉬움 | 번거로움 bean 인스턴스 생성 시 패키지 이름을 포함하여 클래스의 전체 이름을 지정해야함 특정 구문을 따라야 하고, 자동 와이어링 과정 등 구문이 다소 복잡함 |
Clean POJOs | Yes | No(POJO는 스프링 프레임워크애 대해 전혀 알지 못한다.) |
쉬운 유지관리 | No | Yes |
사용 빈도 | 극히 드믐 | 자주 사용 |
디버깅 | Medium | Hard |
❗️XML과 어노테이션 설정은 혼용하지 않는 것이 좋다.
📂 참고자료