[F-Lab 모각코 챌린지 41일차] Spring Bean

부추·2023년 7월 11일
0

F-Lab 모각코 챌린지

목록 보기
41/66

TIL

초간단 스프링
Bean이란? 등록방법은?



1. 초간단 스프링

스프링이란, 자바로 웹 어플리케이션을 제작할 수 있도록 하는 프레임워크이다. 과연 객체 지향 언어인 자바의 속성에 맞게 객체 지향적으로 웹 어플리케이션을 작성할 수 있도록 해주는 프레임워크로서 IoC, DI, AOP 등의 특징을 가진다.


1) IoC (Inversion Of Control)

스프링에서는 new를 통해 개발자가 직접 클래스 인스턴스를 생성하지 않고, 스프링의 IoC 컨테이너가 이를 대신한다. 코드에 지정된 스캔 범위의 컴포넌트와 빈을 스캔하고 이를 application context에 등록하여, 프로그램의 수행 중 context에 등록된 객체들을 사용할 수 있게 해준다. 프로그래머가 아닌 프레임워크가 객체 관리를 대신한다고 하여 객체 제어권이 역전되었다, Inversion Of Control이 일어났다 라고 표현한다.

프로젝트가 커지면 객체를 생성하고 이들의 생명주기를 관리하는 일이 복잡하고 어려워질 수 있는데, 객체들의 생명주기 관리를 프로그래머가 아닌 스프링이 대신 해주므로 객체 생성 및 관리 자체에 대한 코드가 줄어 코드가 조금 더 간단해진다.


2) DI (Dependency Injection)

객체 지향 프로그래밍에선 기능이 객체들의 상호 의존과 협력을 통해 달성된다. 그러기 위해서 생성자, setter 등의 방법으로 객체 간 의존성을 설정해주는 과정이 필요한데, 스프링에서는 이런 의존성을 코드 내부가 아닌 xml 설정파일 혹은 어노테이션을 통해 관리한다.

이렇게 되면 런타임에 객체들의 의존 관계를 자유롭게 설정할 수 있다. 프로그램을 클래스 모듈 별로 분리하여 모듈 간 결합도를 낮출 수 있다. 결합도를 낮추면 많은 장점이 있다. 다양한 환경에서 프로그램 실행 시마다 사용할 컴포넌트를 갈아끼울 수 있고, 그렇게 되면 코드 테스트 역시 간편해진다(테스트 환경에 알맞은 모듈을 컴포넌트로 등록하면 되므로). 느슨한 결합으로 인해 코드나 기능 변화에도 유연하게 대처할 수 있고, 같은 모듈을 재사용하기도 쉬워진다.


3) AOP (Aspect Oriented Programming)

프로그램 기능을 개발하다보면, 주요 로직 이외에 부가적인 기능을 위한 코드가 반복적으로 자주 나타나는 경우가 있다. DB 접근 시 트랜잭션 관리라든가, logging이라든가, security 관련 작업들이 그 예시이다. 이렇게 메인 비즈니스로직과 크게 관련 없는 로직을 "횡단 관심사"라고 부르는데, 스프링에서는 이런 횡단 관심사를 "aspect"로 분리하여 관리할 수 있도록 한다.

공통된 횡단 관심사는 여러 객체에 공통으로 적용할 수 있다. 스프링은 aspect로써 따로 분리한 횡단 관심사를 여러 곳에서 재사용할 수 있는 기능을 제공한다. 이로써 코드 볼륨과 재사용성을 높이는 것이 가능하다.




2. Bean

Bean이란, 앞서 설명한 Spring IoC 컨테이너에 의해 관리되는 자바 객체이다. IoC 컨테이너는 개발자 대신 프로그램 객체, 즉 Bean을 생성하고 관리한다. IoC 컨테이너는 개발자가 XML파일, 혹은 어노테이션을 이용해 config한 Bean들을 application context에 등록한다. 실제로 객체를 만들고 등록하고 DI를 수행하는 것은 컨테이너이지만, 이를 설정하는 것은 개발자의 책임이라는 말이다.
그럼 application context에 어떻게 bean을 추가할 수 있을까?


1) @Bean

@Configuration 클래스 내부에 메소드를 두고 객체를 반환하는 방법이다.

@Configuration
public class ByAnnotation {
    @Bean
    Person person() {
        return new Person("부추");
    }
}

위 코드에서, person()메소드를 통해 반환된 객체는 Bean으로서 컨텍스트에 등록된다. 아래 코드로 확인해보자.

@SpringBootApplication
public class Application {
	public static void main(String[] args) {
		ApplicationContext context = new AnnotationConfigApplicationContext(ByAnnotation.class);
		Person person = context.getBean(Person.class);
		System.out.println(person.getName());
	}

}

결과값은 "부추"로 잘 나온다.

그런데, getBean()의 인자를 보면 Person.class라는 클래스 자체가 들어간다. 그러니까 컨텍스트에 등록된 bean을 구별하기 위해 class type이 사용되고 있다는 뜻인 것 같은데, 그렇다면 같은 타입의 빈이 여러개 있으면 어떻게 될까? @Configuration 클래스에 person2()를 추가했다.

@Configuration
public class ByAnnotation {
    @Bean
    Person person() {
        return new Person("부추");
    }

    @Bean
    Person person2() {
        return new Person("부추2");
    }
}

그리고 main을 돌리면?

Exception in thread "main" org.springframework.beans.factory.NoUniqueBeanDefinitionException:
No qualifying bean of type 'com.buchu.spring.bean.Person' available:
expected single matching bean but found 2: person,person2

NoUniqueBeanDefinitionException 발생. class type으로 구분이 안되는 여러 개의 빈들이 있으니 어떤 것을 불러와야할지 모르는 예외가 발생했다.

대부분의 경우에 하나의 타입은 하나의 빈만 생성한다. 그러지 않더라도 profile 등의 방법을 통해 특정 실행 상황에선 특정 빈을 이용하도록 프로그램 단위의 configuration을 진행한다.

그건 그거고, context 레벨에서 bean을 구분할 수 있게 해주는 몇 가지 방법을 살펴보자. getBean()의 오버로딩 목록 중, 첫번째 인자로 bean의 이름을, 두번째 인자로 bean 타입을 가져가는 메소드가 있다. bean의 이름은 어떻게 정해질까?

  1. 기본적으로 bean 이름은 @Bean 메소드의 이름을 따라간다.
  2. @Bean(name = "custom")으로 bean 이름을 설정할 수도 있다.

ByAnnotation에서 메소드 이름이 각각 person(), person2()였으므로 빈 이름 역시 personperson2일 것이다. 과연 진짜일지 확인해보자.

@SpringBootApplication
public class Application {
	public static void main(String[] args) {
		ApplicationContext context = new AnnotationConfigApplicationContext(ByAnnotation.class);

		Person buchu1 = context.getBean("person",Person.class);
		System.out.println(buchu1.getName());

		Person buchu2 = context.getBean("person2",Person.class);
		System.out.println(buchu2.getName());
	}
}

위 코드를 돌리면?

부추
부추2

ㅇㅋㅇㅋ

또한, @Primary가 붙은 bean은 해당 타입의 빈을 불러왔을 때 default로 로딩된다. ByAnnotation 클래스의 person2() 메소드 위에 @Primary 어노테이션을 붙이고 다음 코드를 수행해보자.

Person person = context.getBean(Person.class);
System.out.println(person.getName());

빈 이름을 설정해주지 않았는데도, 아까와 같이 예외가 발생하지 않고 "부추2"가 출력될 것이다.


2) @Component

@Configuration.. @Bean... 하기 귀찮으니 아예 class 선언 시에 Bean으로 선언해버리자고 등장한 어노테이션이다. Person 클래스 위에 @Component를 떡. 붙이자.

@Setter
@Component
public class Person {
    private String name;
    public void sayHello() {
        System.out.println("안녕! 내 이름은 "+name+"!");
    }
}

그리고 @Configuration 클래스에, @ComponentScan 어노테이션을 추가하는 것이다.

@Configuration
@ComponentScan(basePackages = "com.buchu.spring.component")
public class ByAnnotation {
}

보면 알겠지만, basePackages 속성에는 컴포넌트 클래스들이 들어있는 패키지를 루트 디렉토리부터 따라가는 포맷으로 추가해야한다.

그 뒤 main메소드를 다음과 같이 구성하고 실행해보자.

@SpringBootApplication
public class Application {
	public static void main(String[] args) {
		ApplicationContext context = new AnnotationConfigApplicationContext(ByAnnotation.class);

		Person person = context.getBean(Person.class);
		person.setName("부추");
		person.sayHello();
	}
}

결과는?

안녕! 내 이름은 부추!

예상한 대로다. 이렇게 @Component + @ComponentScan 조합으로 Bean을 등록할 수도 있다는 점을 알아두자. 그리고 기본적으로 컴포넌트를 통한 등록은 싱글톤이다. 하나의 클래스 타입 당 하나의 빈만 컨텍스트에 등록되는 것.. 그리고 @Component로 빈을 등록하기 위해선 생성자가 있어야한다. 생성자를 호출함으로써 Bean 객체를 만드므로 당연한 얘기..

내일 @AutoWired를 이용한 DI에 대해 얘기할 때 생성자를 통한 의존성 주입 역시 얘기할 예정인데, 의존성 주입 및 생성자와 관련한 @Component는 그때 보도록 하자.

# Stereotype Annoatation

Bean들 중에서도, 특별한 일을 수행하는 빈들을 표지하기 위해 사용하는 어노테이션들이다. spring으로 웹 어플리케이션을 개발할 때 엄청 자주 쓰는 놈들이다.

  • @Controller : MVC 구조에서 controller 역할을 하는 빈에게 붙인다. @RequestMapping 등의 핸들러 메소드들이 존재한다.
  • @Service : 레이어드 아키텍처에서, 비즈니스 로직이 존재하는 service 클래스에 붙이는 어노테이션이다.
  • @Repository : DB 접근과 관련있는 클래스에 붙이는 어노테이션이다.



REFERENCE

https://jerryjerryjerry.tistory.com/62

profile
부추튀김인지 부추전일지 모를 정도로 빠싹한 부추전을 먹을래

0개의 댓글