오봉이와 함께하는 개발 블로그

Spring - 스프링 싱글톤 & 어노테이션을 통한 DI 본문

BE/Spring

Spring - 스프링 싱글톤 & 어노테이션을 통한 DI

오봉봉이 2022. 1. 5. 16:11
728x90

스프링 Singleton

  • 스프링 컨테이너는 빈을 생성할 때 싱글톤 패턴을 적용하지 않아도 항상 클래스당 1개의 인스턴스만 생성(디폴트)
  • singleton 속성 변경 가능
    • <bean> 태그의 scope 속성을 이용하여 빈이 싱글톤으로 생성되게 할지 아니면 요청할 때 마다 생성되게 할지 설정 가능
  • singleton : 컨테이너에 한 개의 인스턴스만 생성 (기본값)
  • prototype : 빈을 요청할 때마다 인스턴스 생성
  • thread : 쓰레드별로 생성
  • request / session / application 스코프 있음

Annotation을 이용한 DI

  • xml 설정 파일에서 <bean> 태그를 이용해서 설정하였던 빈 설정을 Annotation(메타데이터)을 이용해서 자바 코드에서 설정
  • 예: xml 설정 파일에서 <bean>을 설정하지 않고
    • 스프링이 자바 소스 코드를 읽어서 클래스에 @Component 어노테이션이 붙은 클래스를 객체화 (bean 설정)
    • A1 클래스의 객체를 A2 클래스의 객체로 변경하려면 A1 클래스에서 @Component를 제거하고 A2 클래스에 @Component를 붙이면 됨
    • @Autowired 어노테이션을 사용하여 bean을 자동 삽입

xml 설정 파일에 context 네임스페이스 추가

  • 빈 설정을 위한 어노테이션을 사용하기 위해서는 설정 파일에 context 네임스페이스가 추가되어 있어야 함
    • [Namespaces] 탭에서 추가
  • <context:component-scan> 태그 이용하여 빈으로 등록될 클래스 패키지 지정
    • 의미 : 자바 소스 코드에서 @Component로 등록된 클래스를 찾아서(scan) 클래스를 객체화(빈 설정)

스프링에서 사용하는 Annotation 종류

  • DI 관련 Annotation
    • xml 설정 파일에 있는 /에 대해 DI하거나
    • 자바 코드에서 생성된 bean에 대해 DI 할 수 있음
      • @Autowired
        • 타입을 기준으로 의존성 주입
        • 스프링 빈에 의존하는 다른 빈을 자동으로 주입할 때 사용
        • 스프링에서 지원
        • required 속성
          • 의존 객체를 주입하지 않아도 될 때 사용
            • @Autowired(required=false)
            • xml 파일에 <bean>이 존재하지 않아도 오류 발생하지 않음
            • 디폴트 : true
        • 위치
          • 필드 위에
          • 생성자 위에
          • Setter 메소드 위에
      • @Inject
        • @Autowired와 동일 (자바에서 지원)
      • @Qualifier
        • 특정 빈의 이름을 지정
        • 동일한 interface를 구현한 클래스가 여러 개 있는 경우 사용하고자 하는 특정 빈의 이름을 지정할 때 사용
      • @Resource
        • @Autowired와 @Qualifier를 같이 사용하는 것과 동일
        • 자바에서 지원
        • pom.xml에서 <dependency> 추가
          • <dependency>
            • <groupId>javax.annotation</groupId>
            • <artifactId>javax.annotation-api</artifactId>
            • <version>1.3.2</version>
          • </dependency>
  • 빈 생성 관련 Annotation
    • 빈 생성(설정)을 위해 클래스 위에 추가되는 어노테이션
    • 클래스 이름 위에 붙이면 해당 클래스 파일에 대한 bean 자동 생성 (xml 파일에서 bean 생성하지 않음)
    • 생성되는 빈의 이름은 클래스 이름에서 첫 문자만 소문자
      • 예: NameService 클래스의 빈 이름은 nameService
    • xml 설정 파일에서 필요한 작업
      • xml 설정 파일에 context 네임스페이스 추가
      • <context:component-scan base-package=”패키지명” />
        • @Component 어노테이션이 적용된 클래스를 빈으로 등록
        • 빈으로 등록될 클래스가 들어 있는 패키지 지정
        • 상위 패키지를 지정하면 하위 패키지까지 빈으로 등록될 클래스를 찾음
        • <context:annotation-config /> 필요 없음
    • @Component
      • 일반적인 컴포넌트로 등록되기 위한 클래스에 사용
      • 클래스를 빈으로 등록(부품 등록)
      • 빈 id를 지정할 수 있음
      • @Component("빈 이름")
        • xml의 <bean id="빈 이름">에 해당
      • @Controller
        • 컨트롤러 클래스에 사용 (의미론적)
      • @Service
        • 서비스 클래스에 사용 (의미론적)
      • @Repository
        • DAO 클래스 또는 Repository 클래스에 사용 (의미론적)
    • Spring MVC 구성에 따른 어노테이션 사용
      • Controller로부터 데이터 서비스를 위임 받아서 사용자의 요구 사항에 맞는 서비스 제공
      • 업무 단위 또는 트랜잭션 단위로 서비스 담당
      • DAO로 부터 데이터 제공 받음
      • DAO를 통해서 데이터를 DB에 저장
    • @Configuraton
      • 빈 설정 클래스임을 나타내는 어노테이션
    • @Bean
      • 빈을 생성해서 반환
      • 빈을 생성하는 메소드 앞에 기술
      • @Bean이 붙은 메소드는 반드시 Bean 메소드 반환
      • @Bean 어노테이션을 사용하는 경우
        • 일반적으로 @Controller, @Service, @Repository 어노테이션을 붙이지 않는 클래스의 빈 생성에 사용

XML 설정 vs Annotation 설정

DI 관련 Annotation 예제 - applicationContext.xml, INameService.java, BNameService.java, NameController.java, AnotherNameService.java, , NameService.java, NameMain.java

  • @Autowired 어노테이션 사용해서 DI 설정
  • @Qualifier 어노테이션 사용해서 DI 설정
    • 동일한 interface를 구현한 클래스가 여러 개 있는 경우
    • 사용하고자 하는 특정 빈의 이름을 지정할 때 사용
    • @Autowired 어노테이션과 같이 사용
      • @Autowired
      • @Qualifier("anotherNameService")
<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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
    <!--
        ...
    -->
    <!-- Annotation을 사용하기 위한 태그 추가 -->
    <context:annotation-config />
    <!-- 빈 생성 -->
    <bean id="bnameService" class="com.di.spring_di_annotation.BNameService" />
    <bean id="anotherNameService" class="com.di.spring_di_annotation.AnotherNameService" />
    <bean id="nameService" class="com.di.spring_di_annotation.NameService" />
    <bean id="nameController" class="com.di.spring_di_annotation.NameController" />
    <!-- 빈만 설정하고 DI 설정 하지 않았음  -->
// 인터페이스 : 규격을 정해 놓은 것
// 이 인터페이스를 구현할 클래스에서 반드시 포함시켜야 할 메소드를 규격으로 지정해 놓은 것임
public interface INameService {
    // INameService 인터페이스를 구현할 클래스에서
    // 반드시 오버라이드 시켜야할 추상 메소드
    public String showName(String name);
}
public class BNameService implements INameService {
    @Override
    public String showName(String name) {
        System.out.println("BNameService showName() 메소드");
        String myName = "내 이름은 " + name + " 입니다.";
        return myName;
    }
}
package com.di.spring_di_annotation;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

import javax.annotation.Resource;

public class NameController {
    // 인터페이스 타입으로 선언
    // @Autowired 어노테이션 사용해서 nameService 빈을 자동으로 주입
    // 해당 타입의 빈을 찾아서 필드(변수)에 할당
    //@Autowired
    //@Qualifier("anotherNameService")
    //@Qualifier("bnameService")
    //@Resource(name="anotherNameService")
    @Resource()
    INameService nameService;

    // 생성자 없음
    // Setter 메소드를 통해 외부에서 주입 받음
    public void setNameService(INameService nameService) {
        this.nameService = nameService;
    }

    public void show(String name) {
        System.out.println("NameController : " + nameService.showName(name));
    }
}
public class AnotherNameService implements INameService {
    @Override
    public String showName(String name) {
        System.out.println("AnotherNameService showName() 메소드");
        String myName = "내 이름은 " + name + " 입니다.";
        return myName;
    }
}
public class NameService implements INameService {
    @Override
    public String showName(String name) {
        System.out.println("NameService showName() 메소드");
        String myName = "내 이름은 " + name + " 입니다.";
        return myName;
    }
}
public class NameMain {
    public static void main(String[] args) {
        AbstractApplicationContext context = new GenericXmlApplicationContext("applicationContext3.xml");
        // 컨테이너에서 컴포넌트(빈) 가져옴
        NameController controller = context.getBean("nameController", NameController.class);
        controller.show("박길동");
        context.close();
    }
}

빈 생성 어노테이션 예제 - applicationContext4.xml, INameService.java, NameService.java, NameController.java, NameMain.java

  • NameService 클래스 : 빈 생성
    • @Component 어노테이션 추가
  • NameController 클래스 : 빈 생성
    • @Component 어노테이션 추가
    • NameService 클래스 객체 사용
  • NameMain 클래스
    • applicationContext4.xml 사용
    • NameController 클래스 객체 사용
    • @Component 어노테이션을 사용하여 클래스의 빈으로 등록되었으므로 getBean() 메소드로 가져다 사용
  • 설정 파일
    • application-context3.xml
      • xml 설정 파일에 context 네임스페이스 추가
      • <context:component-scan base-package=”패키지명” />
    <context:component-scan base-package="com.di.spring_di_annotation_component" />
package com.di.spring_di_annotation_component;

// 인터페이스 : 규격을 정해 놓은 것
// 이 인터페이스를 구현할 클래스에서 반드시 포함시켜야 할 메소드를 규격으로 지정해 놓은 것임
public interface INameService {
    // INameService 인터페이스를 구현할 클래스에서
    // 반드시 오버라이드 시켜야할 추상 메소드
    public String showName(String name);
}
package com.di.spring_di_annotation_component;

import org.springframework.stereotype.Component;

//NameService 클래스를 빈으로 등록
// 생성된 빈 이름은 nameService
@Component
public class NameService implements INameService {
    @Override
    public String showName(String name) {
        System.out.println("NameServie showName() 메소드");
        String myName = "내 이름은 " + name + " 입니다.";
        return myName;
    }
}
package com.di.spring_di_annotation_component;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

// NameController 클래스를 빈으로 등록
// 생성된 빈 이름은 nameController
// NameMain에서 사용
@Component
public class NameController {
    @Autowired
    INameService nameService;
    public void show(String name) {
        System.out.println("NameController : " + nameService.showName(name));
    }
}
package com.di.spring_di_annotation_component;

import com.di.spring_di_annotation.NameController;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;

public class NameMain {
    public static void main(String[] args) {
        AbstractApplicationContext context = new GenericXmlApplicationContext("application_Context4.xml");
        // 컨테이너에서 컴포넌트(빈) 가져옴
        NameController controller = context.getBean("nameController", NameController.class);
        controller.show("컴포넌트");
        context.close();
    }
}

@Configuration / @Bean 어노테이션 예제 - ApplicationConfig.java, BMI.java, Member.java, MemberMain.java

package com.di.spring_di_annotation_configuration_bean;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import java.util.ArrayList;

@ComponentScan("com.di.spring_di_annotation_configuration_bean")
@Configuration
public class ApplicationConfig {
    @Bean
    public BMI bmi() {
        BMI bmi = new BMI();
        return bmi;
    }
    @Bean
    public Member member() {
        ArrayList<String> courses = new ArrayList<String>();
        courses.add("수영");
        courses.add("헬스");
        courses.add("에어로빅");

        Member member = new Member();
        member.setBmi(bmi());
        member.setName("홍길동");
        member.setAge(20);
        member.setHeight(170);
        member.setWeight(70);
        member.setCourses(courses);

        return member;
    }
}
package com.di.spring_di_annotation_configuration_bean;

public class BMI {
    public void showBMI(float height, float weight) {
        float bmi = weight/(height * height) * 10000;

        String result;
        if(bmi >= 25) {
            result = "비만입니다.";
        }
        else if(bmi < 25 && bmi >= 23) {
            result = "과체중입니다.";
        }
        else if(bmi < 23 && bmi >= 18.5) {
            result = "정상";
        }
        else {
            result = "저체중";
        }
        System.out.println("BMI 지수 : " + bmi + " - " + result);
    }
}
package com.di.spring_di_annotation_configuration_bean;

import com.di.spring_di_xml_constructor_value.BMI;

import java.util.ArrayList;

public class Member {
    private BMI bmi;
    private String name;
    private int age;
    private float height;
    private float weight;
    private ArrayList<String> courses;

    // 생성자 기반 DI
    public Member(BMI bmi, String name, int age, float height, float weight, ArrayList<String> courses) {
        this.bmi = bmi;
        this.name = name;
        this.age = age;
        this.height = height;
        this.weight = weight;
        this.courses = courses;
    }

    public BMI getBmi() {
        return bmi;
    }
    public void setBmi(BMI bmi) {
        this.bmi = bmi;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public float getHeight() {
        return height;
    }
    public void setHeight(float height) {
        this.height = height;
    }
    public float getWeight() {
        return weight;
    }
    public void setWeight(float weight) {
        this.weight = weight;
    }
    public ArrayList<String> getCourses() {
        return courses;
    }
    public void setCourses(ArrayList<String> courses) {
        this.courses = courses;
    }
    public void showBMI() {
        bmi.showBMI(height, weight);
    }

    @Override
    public String toString() {
        return "Member{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", height=" + height +
                ", weight=" + weight +
                ", courses=" + courses +
                '}';
    }
}
package com.di.spring_di_annotation_configuration_bean;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MemberMain {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ApplicationConfig.class);
        Member member = context.getBean("member", Member.class);
        System.out.println(member); // toString() 자동 호출
        member.showBMI();
        context.close();
    }
}
728x90

'BE > Spring' 카테고리의 다른 글

Spring - Controller와 요청 처리  (0) 2022.01.07
Spring - 모델 패턴  (0) 2022.01.06
Spring - AOP 간단한 기초 설명  (0) 2022.01.05
Spring - 의존성, DI(Dependency Injection)  (0) 2022.01.04
Spring - 스프링 개요  (0) 2022.01.04
Comments