JAVA/Spring

DI(Dependency Injection) 이란? / (포스팅 하나로 세부내용까지 총 정리)

min민 2023. 3. 13.

 

 

 

TIL 목표:

  • Spring 컨테이너(Container)와 빈(Bean)의 의미를 이해할 수 있다.
  • 빈 스코프(Bean Scope)의 의미를 이해할 수 있다.
  • Java 기반 컨테이너(Container) 설정에 대해 이해할 수 있다.
  • Spring DI(Dependency Injection)의 의미를 이해할 수 있다.
  • Component 스캔에 대해 이해할 수 있다.

 

 

1️⃣ DI란? (의존성 주입)

 

생성자 뿐 아니라 다른 방식으로도 의존성 주입이 가능하지만,

일반적으로는 생성자를 통한 의존관계 주입을 사용한다.

 

 

 

2️⃣ 스프링 컨테이너 (Spring Container)

스프링 프레임워크(Spring Framework)는 자바 기반의 오픈소스 프레임워크로서, 자바 애플리케이션을 개발하기 위한 다양한 기능을 제공한다. 그 중에서도 스프링 컨테이너(Spring Container)는 스프링 프레임워크의 핵심이다. 스프링 컨테이너는 스프링 애플리케이션에서 객체를 생성하고 관리하는 역할을 담당한다.

 

스프링 컨테이너는 내부에 존재하는 애플리케이션 빈의 생명주기를 관리한다.

* Bean 생성, 관리, 제거 등의 역할을 담당합니다.

 

스프링 컨테이너가 뭐야?

ApplicationContext를 스프링 컨테이너라고 하고 인터페이스로 구현되어 있다. (다형성 적용)

의존성 주입을 통해 애플리케이션의 컴포넌트를 관리한다.

 

 

왜써?

객체 간의 의존성을 낮추기 위해 Spring 컨테이너가 사용된다.

스프링 컨테이너를 사용하면서 구현 클래스에 있는 의존을 제거하고 인터페이스에만 의존하도록 설계할 수 있다.

 

2-1 스프링 컨테이너의 종류

스프링 컨테이너에는 크게 두 가지 종류가 있다.

 

2-1.1 BeanFactory

BeanFactory는 스프링 컨테이너의 최상위 인터페이스다. 객체를 생성하고 관리하는 기능을 제공한다.

BeanFactory는 지연 초기화(lazy initialization) 방식을 사용하며, getBean() 메서드를 호출할 때 해당 객체를 생성한다.

 

2-1.2 ApplicationContext

ApplicationContext는 BeanFactory를 상속받은 인터페이스로, BeanFactory가 제공하는 모든 기능을 포함하며, 추가적으로 다양한 기능을 제공한다. ApplicationContext는 미리 객체를 생성하여 캐싱해두고, getBean() 메서드를 호출할 때 캐싱된 객체를 반환한다. 또한, 스프링에서 제공하는 다양한 이벤트, 메시지 등의 기능도 사용할 수 있다.

 

3 스프링 컨테이너에서 객체 생성하기

3.1 xml 설정 파일을 사용하는 경우

잘 사용하지 않으니 간단하게 작성하면,

 

<bean 태그를 사용하여 객체를 생성하고 설정할 수 있다.>

<bean id="student" class="com.example.Student">
  <property name="name" value="미니" />
  <property name="age" value="25" />
</bean>

id가 student인 Student클래스의 객체를 생성하고, name과 age의 프로퍼티를 설정하는 방법이다.

 

 

 

3.2 자바 어노테이션

 

간단하게 @Component 어노테이션을 클래스에 추가하면 된다.

@Component
public class Student {
  private String name;
  private int age;

위 코드는 @Component 어노테이션을 사용하여 Student 클래스를 스프링 빈으로 등록하는 방법이다.

 

 

3.3 자바 코드

자바 코드를 사용하는 경우, @Configuration 어노테이션을 추가한 클래스에서

@Bean 어노테이션을 사용하여 객체를 생성하고 설정할 수 있다.

 

@Configuration
public class AppConfig {
  @Bean
  public Student student() {
    Student student = new Student();
    student.setName("미니");
    student.setAge(25);
    return student;
  }
}

위 예시는 AppConfig 클래스에서 student() 메서드를 사용하여 Student 클래스의 객체를 생성하고, setName()과 setAge() 메서드를 호출하여 프로퍼티를 설정하는 예시다.

 

4. 스프링 컨테이너에서 객체 사용하기

스프링 컨테이너에서 생성된 객체를 사용하려면 getBean() 메서드를 호출하면 된다.

getBean() 메서드는 스프링 빈의 id나 이름을 인자로 받는다.

Student student = (Student) applicationContext.getBean("student");

위 코드는 ApplicationContext에서 id가 "student"인 빈을 가져와서 Student 클래스의 객체로 형변환하여 사용하는 코드다.

 

 

5. 스프링 컨테이넌의 장점

스프링 컨테이너를 사용하면 객체를 생성하고 관리하는 코드를 직접 작성할 필요가 없으므로, 개발자가 비즈니스 로직에 집중할 수 있다. 또한, 객체의 라이프사이클을 관리하여 메모리 누수 등의 문제를 예방할 수 있으며, 객체의 의존성을 자동으로 주입하여 코드의 유연성과 확장성을 높일 수 있다.

 

 

결과적으로 스프링 컨테이너는 스프링 프레임워크 사용시에 개발 효율성을 높일 수 있는 주요 개념이다.

 

 

*프로퍼티란?

프로퍼티(property)는 객체의 속성을 나타내는 값이며, 객체가 가지고 있는 데이터를 저장하는 데 사용된다.

일반적으로 클래스의 멤버 변수로 선언하며, getter와 setter 메서드를 통해 접근한다.

 

예를 들어, 학생 객체를 나타내는 Student 클래스가 있다고 가정해면,

이 클래스는 이름과 나이라는 두 개의 속성을 가질 수 있다.

이때, 이름과 나이는 Student 클래스의 프로퍼티(property)다.

 

public class Student {
  private String name;
  private int age;

  // getter, setter 메서드
}

위 코드에서 name과 age는 프로퍼티로 선언되어 있으며, 이 프로퍼티들은 객체의 데이터를 저장하고, getter와 setter 메서드를 통해 접근할 수 있다. 예를 들어, getName() 메서드를 호출하면 학생의 이름을 반환하고, setName() 메서드를 호출하면 학생의 이름을 설정할 수 있다.

 

 

 

3️⃣ 빈(Bean)

 

1. 빈이란?

 

빈(bean)은 인스턴스화된 객체를 의미한다.

스프링 컨테이너에 등록된 객체(관리되는 객체)를 스프링 빈이라고 한다.

@Bean이 적힌 메서드를 모두 호출해서 반환된 객체를 스프링 컨테이너에 등록할 수 있다.

 

빈(Bean)은 스프링 컨테이너에 의해 생성되고, 관리되는 객체를 말한다.

스프링에서는 개발자가 작성한 자바 클래스를 빈으로 등록하여, 컨테이너에서 관리할 수 있다. 이렇게 등록된 빈은 컨테이너에서 필요할 때마다 가져와 사용할 수 있다.

 

빈(Bean)은 일반적으로 객체지향 프로그래밍에서 객체(Object)와 같은 의미로 사용된다.

하지만, 스프링에서는 빈(Bean)을 객체(Object)보다 좀 더 포괄적인 의미로 사용하며,

스프링의 빈(Bean)은 객체(Object)보다 더 많은 역할을 수행한다.

 

2. 빈(Bean) 등록 방법

빈(Bean)을 등록하는 방법에는 크게 3가지가 있다.

 

위의 스프링 컨테이너에서 예시 코드로 작성한것 처럼, xml , 어노테이션, 자바 코드를 활용해 등록할 수 있다.

 

1. XML 설정 파일에서는 <bean> 태그를 사용하여 빈(Bean) 등록

2. 어노테이션을 이용하여 빈(Bean)을 등록 / 주로 @Component 어노테이션을 사용

3.  Java Config는 자바 코드를 이용하여 빈(Bean)을 등록하는 방법 @Configuration 어노테이션을 사용

 

3. 빈(Bean)스코프

빈(Bean) 스코프는 빈(Bean)이 생성되고, 사용되는 범위를 의미한다. 스프링에서는 다양한 스코프를 제공하며, 주요한 스코프는 다음과 같다.

 

3.1 싱글톤(Singleton)

싱글톤(Singleton) 스코프는 스프링 컨테이너에서 생성된 빈(Bean)이 하나의 인스턴스만 생성되고, 모든 요청에 대해 동일한 인스턴스를 반환하는 것을 의미한다. 이는 스프링의 기본 스코프이며, 따로 설정하지 않으면 모든 빈(Bean)은 싱글톤(Singleton) 스코프로 생성된다.

 

 

3.2 프로토타입(Prototype)

프로토타입(Prototype) 스코프는 스프링 컨테이너에서 생성된 빈(Bean)이 매번 새로운 인스턴스를 생성하는 것을 의한다. 이는 빈(Bean)이 매번 새로 생성되기 때문에, 빈(Bean)을 사용할 때마다 초기화 작업이 수행되며, 빈(Bean)의 상태가 계속 유지되지 않는다.

 

 

3.3 리퀘스트(Request)

리퀘스트(Request) 스코프는 각각의 HTTP 요청마다 새로운 빈(Bean) 인스턴스가 생성되는 것을 의미한다. 이 스코프는 웹 애플리케이션에서 매우 유용하며, 하나의 HTTP 요청에서 사용되는 빈(Bean)은 다른 요청에서 사용되지 않는다.

 

 

3.4 (Session)

세션(Session) 스코프는 각각의 HTTP 세션마다 새로운 빈(Bean) 인스턴스가 생성되는 것을 의미한다. 이 스코프는 웹 애플리케이션에서 사용자의 세션 정보를 유지하거나, 여러 페이지에서 공유해야 하는 빈(Bean)을 생성할 때 사용된다.

 

3.5 (Global Session)

글로벌 세션(Global Session) 스코프는 포털 애플리케이션에서 사용되며, 포털의 세션 정보를 유지하거나, 여러 포털에서 공유해야 하는 빈(Bean)을 생성할 때 사용된다.

 

 

 

스코프가 뭐야? ↓ 아래 글 참고

https://mininkorea.tistory.com/46

 

스코프(Scope) 란? /SpringFramework

스코프(Scope) 란? 스프링 프레임워크에서 빈(Bean) 객체의 생성 및 소멸 시점과 라이프사이클을 관리하는 것 중 하나가 스코프(Scope)다. 스코프는 빈 객체의 생성 범위를 지정하며, 해당 범위에서만

mininkorea.tistory.com

 

 

4️⃣ DI(의존성 주입)이랑 Bean(빈) 은 무슨 관계야?

* 어떤 상관이 있는거지?

 

(DI의 설명)

간단히 말하자면, DI는 객체간의 의존성을 느슨하게 만들어주는 디자인 패턴이다. 이는 객체간의 결합도를 낮추고 유연성과 확장성을 높이는데 도움이 된다. 

 

(Bean의 설명)

Bean은 스프링 컨테이너가 생성 및 관리하는 객체를 의미한다. 스프링에서 Bean은 기본적으로 싱글톤 패턴으로 관리되며, DI를 통해 다른 객체에 주입된다. Bean은 @Component, @Service, @Repository, @Controller 등의 애노테이션을 통해 스프링에서 관리되는 대상으로 지정된다.

 

(DI, Bean 비교)

Bean과 DI의 관계를 살펴보면, DI는 Bean을 주입하는 방법 중 하나다. 즉, DI를 사용하여 의존성을 갖는 객체들간에 Bean을 주입하면서 객체간의 결합도를 낮출 수 있다. 스프링은 DI를 위해 다양한 방법을 제공하며, @Autowired, @Resource, @Inject 등의 애노테이션을 이용해 Bean을 주입한다.

 

 

간단한 예시를 들면,

/*
UserService 클래스에서 UserRepository 클래스를 의존하고 있다고 가정해보자.
이때, UserService 클래스에서 UserRepository 클래스를 직접 생성하면, 
UserService 클래스와 UserRepository 클래스는 강한 결합을 가지게 된다. 
하지만, 스프링의 DI를 이용해 UserService 클래스에서 UserRepository 클래스를 주입받으면, 
UserService 클래스와 UserRepository 클래스는 느슨한 결합을 유지할 수 있다.
*/

 

따라서, Bean과 DI는 스프링에서 밀접한 관련이 있다. Bean은 스프링 컨테이너가 생성 및 관리하는 객체를 의미하며, DI는 Bean을 주입하는 방법 중 하나다. Bean과 DI를 이용해 객체간의 결합도를 낮추고, 유연성과 확장성을 높일 수 있다.

 

 

JAVA 기반 컨테이너 설정방법 ↓ (아래 링크 참고)

https://mininkorea.tistory.com/47

 

Java 기반 컨테이너 설정방법 (Spring 어노테이션 활용)

1️⃣ Java 기반 컨테이너 설정방법 (어노테이션 활용) Java 기반의 컨테이너 설정 방법 중 어노테이션을 활용한 것은 스프링 프레임워크에서 제공하는 애노테이션 기반의 설정 방법이다. 2️⃣ 스

mininkorea.tistory.com

 

 

5️⃣ @ComponentScan

 

5.1 @ComponentScan이란?

@ComponentScan은 스프링 프레임워크에서 사용되는 어노테이션 중 하나로, 스프링 컨테이너가 구성요소들을 찾아서 자동으로 빈으로 등록해주는 기능을 제공한다.

 

@ComponentScan 어노테이션은 주로 Spring Boot에서 사용되며, 해당 어노테이션이 붙은 클래스가 위치한 패키지와 그 하위 패키지를 대상으로 구성요소를 찾아서 빈으로 등록합니다.

 

 

5.2 @ComponentScan 속성

@ComponentScan 어노테이션의 속성으로는 basePackages, value, includeFilters, excludeFilters 등이 있습니다.

  • basePackages 또는 value: 대상 패키지를 지정한다.
  • includeFilters: 빈으로 등록할 대상을 필터링하는 기능을 제공한다.
  • excludeFilters: 빈으로 등록하지 않을 대상을 필터링하는 기능을 제공한다.

@ComponentScan 어노테이션을 사용하면 더 이상 XML 파일이나 Java Config 파일에서 빈을 등록하는 작업을 수동으로 수행하지 않아도 된다. 대신 자동으로 빈으로 등록되어 DI(Dependency Injection) 등 스프링의 기능을 사용할 수 있게 된다.

 

 

EX)

com.example.MINIproject
├── controller
│   ├── UserController.java
│   └── ProductController.java
├── service
│   ├── UserService.java
│   └── ProductService.java
├── repository
│   ├── UserRepository.java
│   └── ProductRepository.java
└── Application.java

이같은 프로젝트가 있다면,

위의 구성요소들 중, 스프링 빈으로 등록해야 하는 클래스는 UserController, ProductController, UserService, ProductService, UserRepository, ProductRepository 등이 있을것이다.

 

 

 

이때, @ComponentScan 어노테이션을 사용하여 아래 코드와 같이 Application 클래스에 설정할 수 있다.

 

@SpringBootApplication
@ComponentScan(basePackages = {"com.example.MINIproject"})
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

 

위와 같이 @ComponentScan 어노테이션의 basePackages 속성에 "com.example.MINIproject"를 지정하면, 스프링 컨테이너는 com.example.MINIproject 패키지와 그 하위 패키지에 있는 구성요소들을 스캔하여 빈으로 등록하게 된다.

 

즉, UserController, ProductController, UserService, ProductService, UserRepository, ProductRepository 클래스는 모두 스프링 빈으로 등록되게 된다.

 

 

 

6️⃣ 다양한 의존관계 주입 방법

의존관계 주입은 객체 지향 프로그래밍에서 중요한 개념 중 하나다.

객체 간의 의존성을 줄이고, 유연한 코드를 작성할 수 있도록 도와준다.

 

 

의존관계 주입은 크게 세 가지 방법으로 구현할 수 있다↓ 

 

1. 생성자 주입(Constructor Injection)

 

생성자 주입은 객체 생성 시 생성자를 통해 의존성을 주입하는 방법이다.

이 방법은 객체 생성 시점에 의존성을 명확하게 알 수 있기 때문에 코드 가독성이 좋고, 테스트 코드 작성이 용이하다.

예를 들어, 다음과 같이 생성자를 통해 의존성을 주입할 수 있다.

 

public class UserService {
    private final UserRepository userRepository;
    
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
    
}

위 코드는     public UserService(UserRepository userRepository) { this.userRepository = userRepository;}

와같이 생성자를 주입해 의존관계를 주입한 사례다.

 

 

2. Setter 주입(Setter Injection)

 

Setter 주입은 객체 생성 후 Setter 메서드를 통해 의존성을 주입하는 방법이다.

이 방법은 생성자 주입에 비해 코드 가독성이 떨어지지만, 의존성이 변경될 때마다 객체를 다시 생성하지 않아도 되기 때문에 런타임 시점에 유연한 코드를 작성할 수 있다.

예를 들어, 다음과 같이 Setter 메서드를 통해 의존성을 주입할 수 있다.

 

public class UserService {
    private UserRepository userRepository;
    
    public void setUserRepository(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
    
}

 

위 코드는  public void setUserRepository(UserRepository userRepository) {this.userRepository = userRepository;}

와 같이 setUserRepository를 통해 setter을 주입한 코드이다.

 

3. 필드 주입(Field Injection)

필드 주입은 필드에 직접 의존성을 주입하는 방법이다. 이 방법은 코드 가독성이 가장 떨어지고, 테스트 코드 작성이 어렵지만, 코드 양이 적어서 작성이 간단하다. 또한, 런타임 시점에 의존성을 변경할 수 없기 때문에 유연성이 떨어진다.

예를 들어, 다음과 같이 필드에 직접 의존성을 주입할 수 있다.

 

 

public class UserService {
    @Autowired
    private UserRepository userRepository;
    
}

다음과 같이 그냥 @Autowired 를통해 ( @Autowired: 빈 간의 의존성을 자동으로 주입) 필드 자체에 의존성을 주입하는 방법이다.

 

 

 

정리

생성자 주입

 

위에서 소개한 세 가지 방법 중에서도 생성자 주입을 권장한다.

  1. 생성자 주입은 코드 가독성이 좋고,
  2. 테스트 코드 작성이 용이하며,
  3. 런타임 시점에 유연성이 높기 때문이다.

하지만, 상황에 따라 다른 방법으로 구현해야 할 수도 있다.

 

 

Setter

 

다른 방법으로 구현해야 할 필요가 있는 경우에는 Setter 주입을 사용할 수 있다.

Setter 주입은 생성자 주입에 비해 코드 가독성이 떨어지기 때문에 코드 양이 많을 때 사용하면 좋다.

Setter 주입을 사용할 때는 생성자를 통해 객체를 생성하고, Setter 메서드를 통해 의존성을 주입하는 것이 좋다.

 

 

필드 주입(@Autowired)

 

마지막으로, 필드 주입은 가능하면 피하는 것이 좋다.

필드 주입은 코드 가독성이 가장 떨어지기 때문에 유지보수가 어렵다.

또한, 런타임 시점에 의존성을 변경할 수 없기 때문에 유연성이 떨어진다.

따라서, 가능하면 생성자 주입 또는 Setter 주입을 사용하고, 필드 주입은 최대한 피하는 것이 좋다.

 

 

 

 

 

#️⃣ 회고

DI에 대해서 전반적인건 전부 정리를 마친것 같다. 

AOP(Aspect Oriented Programming)를 다음 포스팅에 정리하도록하고 Spring MVC정리를 이어가야겠다.

댓글