포스트

Day88 Java프로그래밍 기초(Spring IOC)

Spring IoC

1. property 설정

1) Property

  • Property는 Setter 및 Getter의 이름이다.
  • Spring IoC는 property Editor가 내장되어 있어, String과 Primitive Type을 자동 형 변환을 한다.
  • Editor에 등록되어 있지 않으면 CustumEditor에 등록해야한다.

image

2) 자동형변환

  • Sprint IoC는 Primitive을 자동 형변환 할 수 있다.

    1
    2
    3
    4
    5
    6
    7
    8
    
      <bean id="varName" class="FQname">
          <property name="StringName" value="StringValue"/>
          <property name="IntName" value="IntegerValue"/>
          <property name="BooleanName" value="BooleanValue"/>
          <!-- Date은 자동 형변환을 할 수 없다. 
          <property name="DateName" value="DateValue"/>
          -->
      </bean>
    
  • Factory Menthod로 객체를 변환하여 대입해야한다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
      <bean id="varName" class="FQname">
          <property name="StringName" value="StringValue"/>
          <property name="IntName" value="IntegerValue"/>
          <property name="BooleanName" value="BooleanValue"/>
          <property>
              <bean class="java.sql.Date" factory-method="valueOf">
                  <constructor-arg value="2024-10-04"/>
              </bean>
          </property>
      </bean>
    

3) CustomEdit

  • java.beans.PropertyEditor 인터페이스를 상속받은 PropertyEditorSupport 를 상속받아 구현한다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    public class CustomDateEditor extends PropertyEditorSupport {
      // 이 메서드는 스프링 IoC 컨테이너가 String 타입의 프로퍼티 값을
      // 다른 타입의 값으로 바꿀 때 호출하는 메서드이다.
      @Override
      public void setAsText(String text) throws IllegalArgumentException {
        // 파라미터로 넘어온 String 타입의 프로퍼티 값을
        // 원하는 타입(java.sql.Date)의 값으로 바꿔 내부에 저장한다.
        // => 그러면 스프링 IoC 컨테이너를 이 값을 꺼내서 객체에 주입할 것이다.
        this.setValue(Date.valueOf(text));
      }
    }
    
  • CustomDateEditor 적용하면 Spring IoC에서 자동으로 객체를 생성한다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    
    <bean id="varName" class="FQname">
        <property name="StringName" value="StringValue"/>
        <property name="IntName" value="IntegerValue"/>
        <property name="BooleanName" value="BooleanValue"/> 
        <property name="DateName" value="DateValue"/>
    </bean>
      
    <bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
        <property name="customEditors"> 
            <map>
                <!-- 프로퍼티 에디터를 설정하는 방법
                     key: String 값을 어떤 타입의 값으로 바꿀 것인지에 대한 타입 이름이다.
                     value: 커스텀 에디터(프로퍼티 값 변환기) 클래스 이름이다. 
                     의미?
                     => 스프링 IoC 컨테이너가 프로퍼티 값을 설정할 때 
                        특히 String 값을 java.sql.Date 객체로 바꿔야 할 때 
                        이 클래스를 사용하여 값을 바꾸라는 뜻이다.-->
            
                <entry key="java.sql.Date" 
                       value="com.eomcs.spring.ioc.ex07.c.CustomDateEditor"/>
            </map>
        </property>
    </bean>
    

2. annotation-config

1) 객체주입

  • 의존객체를 주입하는 하기위해서는 bean메서드를 통해 객체를 생성하고 주입한다.

    1
    2
    3
    4
    5
    6
    7
    8
    
    <bean id="varName" class="FQname">
        <property name="propertyName" value="propertyValue"/>
        <property name="propertyName" ref="refName"/>
    </bean>
      
    <bean id="refName" class="FQname">
        <property name="propertyName" value="propertyValue"/>
    </bean>
    

2) 객체 자동 주입

  • @Autowired 애노테이션을 사용하면, 자동으로 객체를 주입한다.
  • Spring IoC컨테이너가 설정 파일에 적혀 있는 대로 객체를 생성한다.
  • AutowiredAnnotationBeanPostProcessor는 생성된 객체에 대해 @Autowired 애노테이션을 검사하여 이 애노테이션이 붙은 프로퍼티 값을 자동 주입하는 일을 한다.

    1
    2
    3
    4
    
    @Autowired
    public setPropertyName2(DIname DIname){
      this.DIname = DIname;
    }
    
  • xml에 BeanPostProcessor를 설정한다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    <bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"/>
      
    <bean id="varName" class="FQname">
        <property name="propertyName1" value="propertyValue"/>
        <property name="propertyName2" ref="refName"/>
    </bean>
      
    <bean id="refName" class="DIname">
        <property name="propertyName3" value="propertyValue"/>
    </bean>
    

3) BeanPostProcessor 구현체

  • 새 기능이 IoC 컨테이너가 생성한 객체를 사용해야 한다면, 객체 생성 후에 그 작업을 수행하면 된다.
  • 빈 생성 후에 어떤 작업을 수행할 객체를 만들고 싶다면 BeanPostProcessor 규칙에 따라 클래스를 만들면 된다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    
    public class MyBeanPostProcessor implements BeanPostProcessor {
      @Override
      public Object postProcessBeforeInitialization( 
          Object bean, String beanName) throws BeansException {
        // XML 설정에서 init-method 속성에 지정된 메서드가 호출되기 전에
        // 이 메서드가 먼저 호출된다.
        System.out.println("postProcessBeforeInitialization()");
        System.out.printf("    => %s : %s\n", //
            beanName, //
            bean.getClass().getName());
        System.out.printf("    => %s\n", bean.toString());
        return bean;
      }
      
      
      @Override
      public Object postProcessAfterInitialization(
          Object bean, String beanName) throws BeansException {
        // XML 설정에서 init-method 속성에 지정된 메서드가 호출된 후에
        // 이 메서드가 호출된다.
        System.out.println("postProcessAfterInitialization()");
        System.out.printf("    => %s : %s\n", //
            beanName, //
            bean.getClass().getName());
        System.out.printf("    => %s\n", bean.toString());
        return bean;
      }
      
    }
    
  • xml에서 MyBeanPostProcessor를 등록하고 새용하면 된다.

    1
    
    <bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"/>
    

4) Autowired 위치

  • @Autowired 애노테이션을 필드(인스턴스 변수)에 붙여도 된다.
  • 의존 객체를 직접 변수에 주입한다.
  • -인스턴스 변수에 직접 의존 객체를 주입한다는 것은 캡슐화(즉 외부에서 직접 인스턴스 변수에 접근하는 것을 막는 기법)를 위배하는 측면이 있기 때문에 이 방식은 “객체지향을 파괴하는 방식”이라는 비난을 받는다.

5) 생성자 주입

  • 생성자를 통해 의존 객체를 주입할 수 있다.
  • AutowiredAnnotationBeanPostProcessor가 이것 또한 처리해준다.
  • 해당 클래스에 기본 생성자가 없을 때, 파라미터를 받는 다른 생성자를 찾아 호출한다.

    1
    2
    3
    
    public modifier(DIclass dIclass) {
        this.dIclass = dIclass;
      }
    

6) Autowired 옵션

  • @Autowired의 required 값은 기본이 true이다.
  • @Autowired(required = false) 이면 해당 객체가 없으면 null을 반환한다.

7) Qualifier 사용법

  • 의존 객체가 여러 개 있을 경우, 주입할 의존 객체의 이름을 지정해야한다.
  • @Qualifier("varName")을 통해 주입할 객체을 정할 수 있다.

    1
    2
    
    @Autowired
    @Qualifier("varName")
    

8) @Resource 사용법

  • @Resource는 스프링 프레임워크가 아닌 자바에서 제공한다.
  • @Autowired + @Qualifier = @Resource의 기능이다.

    1
    
    @Resource("varName")
    

3. context 활용

  • xml에서 context를 활용하면, 별도의 bean태그를 사용하지 않아도 기본적인 객체를 생성할 수 있다.
  • 루트 bean에서 xmlns:context="http://www.springframework.org/schema/context"로 네임스페이스를 지정해야한다.
  • schemaLocation은 "http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"이다

1) context:annotation-config

  • <context:annotation-config/>은 아래 코드를 대신 생성해 준다.

    1
    2
    3
    4
    
    <bean class="org.springframework.context.annotation.ConfigurationClassPostProcessor"/>
    <bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"/>
    <bean class="org.springframework.context.event.EventListenerMethodProcessor"/>
    <bean class="org.springframework.context.event.DefaultEventListenerFactory"/>
    

2) context:component-scan

  • component-scan 태그는 지정된 패키지에서 @Component, @Service, @Repository, @Controller 애노테이션이 붙은 클래스를 찾아서 객체를 자동 생성하도록 명령을 내리는 태그이다.
  • base-package 속성은 어느 패키지의 있는 클래스를 찾아서 등록할 것인지 지정하는 속성이다.
    • @Component : 일반 클래스에 대해 붙인다.
    • @Repository : DAO 역할을 수행하는 클래스에 대해 붙인다.
    • @Service : 비즈니스 로직을 수행하는 클래스에 대해 붙인다.
    • @Controller : MVC 구조에서 컨트롤러 역할을 하는 클래스에 대해 붙인다.
    • @RestController : MVC 구조에서 REST API 컨트롤러 역할을 하는 클래스에 대해 붙인다.
  • component-scan 태그를 추가하면 내부적으로 annotation-config 태그가 자동으로 추가된다.

3) context:component-scan 활용

  • include는 특정 타입의 클래스를 포함해서 생성한다.
  • exclude는 특정 타입의 클래스를 제외하고 생성한다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    
    <context:component-scan base-package="com.eomcs.spring.ioc.ex09">
    <!-- 다음 패키지의 클래스 중에서 @Component,@Service,@Controller,@Repository
         애노테이션이 붙은 것은 객체를 생성한다. -->
    <context:include-filter type="regex" 
        expression="com.eomcs.spring.ioc.ex09.p2.Service2"/>
      
    <!-- 특정 패키지의 특정 클래스를 객체 생성 대상에서 제외하기  -->
    <context:exclude-filter type="regex" 
        expression="com.eomcs.spring.ioc.ex09.p2.Service1"/>
      
    <!-- 특정 애노테이션이 붙은 클래스는 객체 생성에서 제외시킨다. -->
    <context:exclude-filter type="annotation" 
        expression="org.springframework.stereotype.Controller"/>
          
    <!-- 특정 패키지만 제외하기 -->
    <context:exclude-filter type="regex" 
        expression="com.eomcs.spring.ioc.ex09.p4.*"/>
          
    <!-- 특정 패키지에서 지정된 패턴의 이름을 가진 클래스를 제외하기 -->
    <context:exclude-filter type="regex" 
        expression="com.eomcs.spring.ioc.ex09.p5.*Truck"/>
    </context:component-scan>
    

4. AppConfig 사용

1) @Bean

  • Spring에서는 xml의 bean태그 처럼 수동으로 메서드를 생성할 수 있다.
  • AppConfig.java를 활용한 경우 @Bean 에노테이션을 붙인다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    
    //AppConfig.java
    @Bean("varName") // 애노케이션에 지정한 이름으로 리턴 값을 보관한다.
    public Class var() {
      Class c = new Class();
      c.setProperty("var");
      return c;
    }
      
    @Bean // 이름을 지정하지 않으면 메서드 이름을 사용하여 저장한다.
    public Class varName() {
      Class c = new Car();
      c.setProperty("var");
      return c;
    }
      
    //test.java
    public static void main(String[] args) {
      ApplicationContext iocContainer =
              new AnnotationConfigApplicationContext(AppConfig.class);
    }
    

2) Configuration

  • AnnotationConfigApplicationContext에서 파라미터에 클래스 패키지명 사용하면 AppConfig에 @Configuration을 붙여야 Spring에서 인식을 한다.
  • 패키지명을 사용하면, 해당 패키지의 @Component, @Service, @Controller, @Repository의 객체도 자동 생성한다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    
    //AppConfig.java
    public class AppConfig{
      @Bean
      public Class var(){
        Class c = new Class();
        c.setProperty("var");
        return c;
      }
    }
      
    //A.java
    @Component
    public class A {
    }
      
    //test.java
    public static void main(String[] args) {
      ApplicationContext iocContainer =
              new AnnotationConfigApplicationContext("packageName");
    }
      
    // a = packageName.A
    // varName = packageName.Class
    

3) @PropertySource

  • .properties 파일을 데이터를 메모리에 로딩하는 일을 한다.
  • @PropertySource({classpath:path/fileName.properties})로 설정한다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    
    @Configuration
    @PropertySource({
      "classpath:com/eomcs/spring/ioc/ex10/c/jdbc.properties",
      "classpath:com/eomcs/spring/ioc/ex10/c/jdbc2.properties"
    })
    public class AppConfig {
      
      // @PropertySource를 통해 로딩된 프로퍼티 값을 사용하고 싶다면
      // Environment 타입의 객체를 주입 받아라!
      @Autowired
      Environment env;  // Spring IoC 컨테이너는 Environment 객체를 주입해 준다.
      
      // @PropertySource를 통해 로딩된 프로퍼티 값 중에서
      // 특정 값만 필드로 주입 받을 수 있다.
      // => 필드 선언에 @Value 애노테이션을 붙인다.
      // => @Value("${프로퍼티이름}")
      @Value("${jdbc.url}")
      String jdbcUrl;
      
      @Value("${jdbc2.url}")
      String jdbc2Url;
      
      // Environment 객체를 통해 메모리에 보관된 .properties 의 값 가져오기
      @Bean
      public Car car1() {
        System.out.println("car1() 호출: ");
        System.out.println("  " + this.env.getProperty("jdbc.username"));
        System.out.println("  " + this.env.getProperty("jdbc2.username"));
        return new Car(null);
      }
      
      // @Value를 통해 필드로 주입 받은 .properties 값 꺼내기
      @Bean
      public Car car2() {
        System.out.println("car2() 호출: ");
        System.out.println("  " + this.jdbcUrl);
        System.out.println("  " + this.jdbc2Url);
        return new Car(null);
      }
      
      // @Value를 사용하여 파라미터로 .properties 값 주입 받기
      @Bean
      public Car car3(
          @Value("${jdbc.username}") String username,
          @Value("${jdbc2.username}") String username2) {
      
        System.out.println("car3() 호출 :");
        System.out.println("  " + username);
        System.out.println("  " + username2);
        return new Car(null);
      }
      
    }
    

4) @CompnetnScan

  • 태그와 같은 일을 한다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    
    // @ComponentScan
    // => <context:component-scan/> 태그와 같은 일을 한다.
    //
    // 사용법1:
    // => 한 개의 패키지를 지정하기
    @ComponentScan(basePackages = {"com.eomcs.spring.ioc.ex11"})
      
    // => 배열 항목이 한 개일 경우 중괄호({}) 생략 가능
    @ComponentScan(basePackages = "com.eomcs.spring.ioc.ex11")
      
    // 사용법2:
    // => 여러 개의 패키지 지정하기
    @ComponentScan(basePackages = {
        "com.eomcs.spring.ioc.ex11.p1",
        "com.eomcs.spring.ioc.ex11.p2",
        "com.eomcs.spring.ioc.ex11.p3"
    })
      
    // 사용법3:
    // => 특정 패키지나 클래스 제외하기
    @ComponentScan(
    basePackages = "com.eomcs.spring.ioc.ex11",
    excludeFilters = {
        @ComponentScan.Filter(
            type = FilterType.REGEX,
            pattern = "com.eomcs.spring.ioc.ex11.p2.*"
            ),
        @ComponentScan.Filter(
            type = FilterType.ANNOTATION,
            value = org.springframework.stereotype.Controller.class
            )
    })
    
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.