Spring

Spring Framework - Annotation

728x90

참조한 강의 : Spring & Hibernate For Beginners (www.udemy.com/course/spring-hibernate-tutorial/)

 

 

- Annotation ?

Annotation 은 JDK 1.5 버전 이후 부터 추가된 기능으로, 자바 소스 코드에 추가해주는 메타 데이터의 일종이다.

일종의 라벨, 마커? 같은 것을 붙여준다고 보면 된다.

 

메타 데이터라는 것은 사전적으로는 데이터에 대한 데이터값을 말하는데

여기서는 클래스에 어노테이션을 붙였다하면, 이 클래스에 대한 정보값을 말한다.

일상생활을 예로 들면, 어떤 신발을 구매했다하면, 그 신발의 사이즈, 제조 국가, 색상 정보 등 신발 자체에 대한 데이터값을 메타 데이터라 볼 수 있다.

 

어노테이션은 컴파일 시기나 혹은 런타임 시기에 처리된다.

앞선 포스팅에서도 봤던 @Override 가 이 어노테이션에 해당하며 @를 붙여서 사용한다.

 

이 어노테이션은 왜 쓰는걸까?

 

어노테이션이 없었을때는 스프링에대한 모든 설정작업을 XML 파일을 통해서 처리했었다.

그러나 앞선 포스팅에서도 잠깐 봤지만, bean 하나 만들때마다 <bean> 태그를 써야하고 여기에 여러가지 속성값 까지 넣으면 매우 코드가 길어진다. 

만약 수천개의 bean 을 만든다면 매우 코드 양이 길어질 것이고 xml 파일에 대한 가독성도 떨어질 것이다.

 

 

* Annotaion 을 사용했을때의 spring 의 내부작업

어노테이션을 사용할때, spring 내부에서 어떤 일들이 일어날까

 

위에서 마치 어노테이션이 모든 xml 을 대체하는 것 처럼 말했지만, 어노테이션을 스캔하기 위해서는 일단은 xml 파일이 있어야 한다. (다만, bean, property 태그 같은것들을 작성하지 않아도됨)

먼저, 사용할 스프링 프로젝트의 applicationContext.xml 파일에 다음의 구문을 추가해줘야한다.

1
<context:component-scan base-package="package-name" />
cs

 

스프링이 스캔할 패키지 명을 작성해서 해당 패키지에 소속된 모든 자바 파일을 검사한다.

 

어떤 특정한 클래스를 bean 으로 등록하고 싶으면 @Component 어노테이션을 써야한다.

예를들면 이런식이다.

 

- TennisCoach.java

import org.springframework.stereotype.Component;
 
@Component
public class TennisCoach {
    /*    
        중간 부분 생략
    */
}
 
cs

해당 클래스 위에 @Component 어노테이션을 붙여주면, 스프링이 어노테이션들을 스캔할때,

이 클래스가 spring bean 으로 쓰이게 될것임을 알려주고 bean factory 에서 이를 관리하게 된다.

 

@Component 대신에 @Component("foo") 라고 하면

이 클래스의 bean id 값이 foo 가 되며, 

아무것도 안줄때는, 본래 클래스 이름을 PascalCase 로 하는 경우가 대부분인데,

여기서 camelCase 로 변경되어 bean id 값으로 들어간다 (기본 bean id 값임)

 

직접 @Component 를 사용해서 bean 을 찾아내는 코드를 써보자.

 

 

 

- IoC 

(어노테이션에서도 IoC 의 개념은 xml 때랑 똑같다. 코드만 조금 다를뿐임)

 

먼저, applicationContext.xml 파일을 다음과 같이 작성해주자

-applicationContext.xml

<?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"
    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">
 
    <bean id="myLoggerConfig" class="com.luv2code.springdemo.MyLoggerConfig" init-method="initLogger">
        <property name="rootLoggerLevel" value="FINE" />
        <property name="printedLoggerLevel" value="FINE" />
    </bean>
 
    <context:component-scan base-package="com.luv2code.springdemo" />
</beans>
cs

중요한 것은 15번줄이다. 앞서 말했듯 스프링이 어떤 패키지의 어노테이션을 스캔할것인지 알려줘야한다.

 

다음의 3개의 파일을 작성해주자 (앞선 포스팅들의 코드와 거의 똑같다, 그러므로 코드 자체에 대한 설명은 생략함)

 

- Coach.java

public interface Coach {
    public String getDailyWorkout();
}
cs

 

- TennisCoach.java

import org.springframework.stereotype.Component;
 
@Component
public class TennisCoach implements Coach {
    
    @Override
    public String getDailyWorkout() {
        return "Practice your backhand grip";
    }
 
}
cs

 

- AnnotationDemoApp.java

import org.springframework.context.support.ClassPathXmlApplicationContext;
 
public class AnnotationDemoApp {
 
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        
        Coach theCoach = context.getBean("tennisCoach", Coach.class);
        
        System.out.println(theCoach.getDailyWorkout());
        
        System.out.println(theCoach.getDailyFortune());
        
        context.close();
    }
 
}
cs

 

여기서 이전 포스팅들과 다른 점은 @Component 를 사용한다는점 

그리고 @Component 에 bean id 를 부여하지 않았기 때문에 default bean id 값인 tennisCoach 가 들어간다는점이다.

 

그래서 메인함수에서 getBean 을 사용할때, tennisCoach 를 넣어줬다.

 

xml 을 사용할때랑 비교해봤을때, 코드가 다소 간략해졌음을 알 수 있다.

 

 

* Default Bean ID

: 앞에서 @Component 를 사용할때, bean id 를 따로 부여하지 않으면 camelCase 를 기반으로 설정된다고 말했다.

그러면, 이런 경우는 어떻게 될까?

클래스 이름이 RESTFortuneService, DBFortuneService 등 과 같이 맨앞에만 대문자가 아니라, 그 뒤에도 대문자가 같이 나온는 경우는 어떨까

이에 대한 해답은 아래의 사이트에 있다.

docs.oracle.com/javase/8/docs/api/java/beans/Introspector.html#decapitalize(java.lang.String)

 

Introspector (Java Platform SE 8 )

The Introspector class provides a standard way for tools to learn about the properties, events, and methods supported by a target Java Bean. For each of those three kinds of information, the Introspector will separately analyze the bean's class and supercl

docs.oracle.com

bean 의 이름을 설정하는 것은 이 Introspctor 라는 클래스를 거쳐서 이뤄지는데, 이 클래스의 메소드 중 하나인 decapitalize 에 의해서 이름이 결정된다.

 

https://docs.oracle.com/javase/8/docs/api/java/beans/Introspector.html#decapitalize(java.lang.String)

이 함수의 설명을 보면 알겠지만, 앞서말한 대문자가 맨앞에만 있는게 아니라 연속해서 오는경우, 그대로 내버려 둠을 알 수 있다. ("but in the (unusual) special case when there is more than one character and both the first and second characters are upper case, we leave it alone")

즉, 앞에 말한 RESTFortuneService 는 그냥 이 이름 그대로 bean id 가 부여된다는 것이다.

 

 

- Dependency Injection 

어노테이션을 이용한 의존성 주입은 xml 때랑 마찬가지로 생성자를 통한 주입, setter method 이용한 주입, property injection 이 있으며 여기에 field injection 이 있다.

 

1) 생성자를 통한 의존성 주입

먼저 FortuneService.java 파일을 만들어서 TennisCoach.java 에 의존성을 주입해보자

그리고 코드들을 아래와 같이 수정, 추가해보자.

 

- FortuneService.java

public interface FortuneService {
    public String getFortune();
}
cs

 

- HappyFortuneService.java

import org.springframework.stereotype.Component;
 
@Component
public class HappyFortuneService implements FortuneService {
 
    @Override
    public String getFortune() {
        return "Today is your lucky day";
    }
 
}
cs

 

- Coach.java

public interface Coach {
    public String getDailyWorkout();
    
    public String getDailyFortune();
}
cs

 

- TennisCoach.java

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
 
@Component
public class TennisCoach implements Coach {
    
    private FortuneService fortuneService;
    
    @Autowired 
    public TennisCoach(FortuneService fortuneService) {
        this.fortuneService = fortuneService;
    }
    
    @Override
    public String getDailyWorkout() {
        return "Practice your backhand grip";
    }
 
    @Override
    public String getDailyFortune() {
        return fortuneService.getFortune();
    }
 
}
cs

 

주목할점은

1. TennisCoach.java 뿐 아니라 모든 구현부가 되는 클래스 파일들은 @Component 를 써야 한다는 것 (HappyFortuneService.java 이 파일도 spring bean factory 에서 관리해야되기 때문임)

2. 의존성 주입시 @Autowired 가 사용된다는 점 이다.

 

어노테이션을 이용한 의존성 주입의 가장 큰 핵심 키워드는 @Autowired 이다.

 

생성자를 통한 의존성 주입이든, setter method 를 이용한 의존성 주입이든 뭐든간에 어노테이션을 쓰고자 한다면

@Autowired 는 반드시 들어가야 한다.

 

 

* @Autowired 는 옵션값일까?

위 코드에서 @Autowired 를 주석처리하고 실행해도, 스프링이 문제 없이 실행된다.

왜 그럴까?

아래의 사이트를 가면 그 답을 알 수 있다.

docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-autowired-annotation

 

Core Technologies

In the preceding scenario, using @Autowired works well and provides the desired modularity, but determining exactly where the autowired bean definitions are declared is still somewhat ambiguous. For example, as a developer looking at ServiceConfig, how do

docs.spring.io

 

https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-autowired-annotation

spring 4.3 이후 버전 부터, @Autowired 를 쓰는 bean 의 생성자가 오로지 한개라면, @Autowired 가 필수 값이 아니다.

그러나, 여러개의 생성자를 넣어놨다면, 반드시 최소한 하나의 @Autowired 는 써줘야 스프링 컨테이너가 이를 인식 할 수 있다.

 

 

2) setter method 를 이용한 의존성 주입

다음은 setter method 를 이용해서 의존성 주입하는 방법을 보자.

다른 부분은 위 코드와 똑같은데, TennisCoach.java 파일에서 생성자 부분을 지우고

아래의 setter method 를 추가하면 된다.

 

- TennisCoach.java

@Autowired 
public void setFortuneService(FortuneService fortuneService) {
    this.fortuneService = fortuneService;
}
cs

 

이 방법도 생성자를 통한 주입과 똑같이 정상 실행된다.

(참고로 method 를 이용한 의존성 주입 방법에서 어노테이션을 사용할경우 메소드 이름은 아무거나 해도 상관 없다)

 

 

3) Field Injection

이 방법은 생성자, setter method 다 필요 없고 그냥 private FortuneService fortuneService 위에 @Autowired 를 붙여주면 되는 방식이다.

 

- TennisCoach.java

@Autowired
private FortuneService fortuneService;
cs

 

이게 끝이다. 

어떻게 이렇게 짧게 쓰는게 가능한걸까?

그 이유는 자바에서 제공하는 java reflection 이라는 기능 때문이다.

www.oracle.com/technical-resources/articles/java/javareflection.html

 

 

 

4) properties injection

xml 에서는 멤버 변수를 주입할 수도 있었다.

어노테이션을 이용해서 멤버 변수를 의존성 주입할때는 이렇게 하면된다.

 

- 1. properties 파일을 생성한다

sport.properties

foo.email=test@gmail.com
foo.team=royal team
cs

 

- 2. xml 파일에 properties 파일을 로드 시킨다

1
 <context:property-placeholder location="classpath:sport.properties"/>  
cs

 

- 3. @Value 어노테이션을 사용해서 값을 부여한다.

@Value("${foo.email}")
private String email;
    
@Value("${foo.team}")
private String team;
cs

 

 

* 어떤 타입의 의존성 주입을 사용해야 하는가?

: "Choose a style, Stay consistent in your project" 

 

어떤 것을 사용하든 상관없다. 어차피 기능적으로 같기 때문이다. 

1인 개발을 하면 본인이 편한 스타일로, 팀으로 개발하면 팀의 코딩 스타일을 따라서 하면된다.

 

 

* @Qualifier Annotation

앞의 코드는 HappyFortuneService.java 라는 클래스에서 제공하는 기능을 TennisCoach.java 에 의존성 주입하는 형태로 코드를 작성했었다. 그러나 만약 FortuneService.java 의 구현 클래스가 여러개 있으면 스프링이 어떤 클래스를 의존성 주입 시킬까?

 

예를들어 FortuneService.java 의 구현 클래스가 다음과 같이 4가지가 있다고 치면,

 

HappyFortuneService.java

RandomFortuneService.java

DatabaseFortuneService.java

RESTFortuneService.java

 

이 4개가 전부 FortuneService.java 의 구현부라 치자, 그러면 이중 어떤것을 Autowired 해야할까

만약 그냥 앞에서 처럼 @Autowired 만 작성하면 

다음과 같은 오류가 나타난다.

출처 : https://www.udemy.com/course/spring-hibernate-tutorial/

spring 은 프로그래머가 이들 중 어떤것을 구현 클래스로 삼을지 지정하지 않았기 때문에, 어떤 것과 매칭시킬지 선택하지 못하고 오류를 내며 프로그램이 종료된다.

 

이들 중 하나만 사용하겠다고 프로그래머가 반드시 명시를 해야 한다.

 

이를 위한 어노테이션이 @Qualifier 이다.

의존성 주입을 받는 TennisCoach.java 부분에 다음과 같이 @Qualifier 어노테이션을 추가해준다.

 

- TennisCoach.java

@Autowired
@Qualifier("randomFortuneService")
private FortuneService fortuneService;
cs

 

이런식으로 특정한 구현 클래스를 사용하겠다고 명시를 해줘야 스프링이 위의 4개중 어떤 것을 사용할지 선택하고 컴파일을 진행할 수 있다.

 

 

 

- Bean Scope, Life Cycle

1) Bean Scope

xml 때랑 마찬가지로 어노테이션도 Bean Scope 와 Life Cycle 의 개념은 똑같다

다만 문법적인 차이만 약간 있을 뿐이다.

 

먼저 Bean Scope 를 설정하는 방법은 @Scope 어노테이션을 사용하는 것이다.

 

예를들면 다음과 같이 쓸 수 있다.

 

- TennisCoach.java (using singleton)

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
 
@Component
@Scope("singleton")
public class TennisCoach implements Coach {
    /*
        중간 생략
    */
}
 
cs

 

- TennisCoach.java (using prototype)

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
 
@Component
@Scope("prototype")
public class TennisCoach implements Coach {
    /*
        중간 생략
    */
}
 
cs

 

이런식으로 bean scope 를 지정할 수 있다.

 

 

2) Bean Life Cycle

어노테이션을 이용한 Life Cycle 관리에는 다음과 같은 두개의 어노테이션을 쓸 수 있다.

@PostConstruct, @PreDestroy

 

@PostConstruct 는 bean 의 생성자가 생성되고 의존성이 주입된 이후에 실행되는 메소드이며,

@PreDestroy 는 bean 이 소멸되기 이전에 실행되는 메소드이다.

 

코드를 예시로 들면 다음과 같이 쓸 수 있다.

 

- TennisCoach.java

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
 
@Component
@Scope("singleton")
public class TennisCoach implements Coach {
    
    @Autowired
    @Qualifier("randomFortuneService")
    private FortuneService fortuneService;
 
    @PostConstruct
    public void doMyStartupStuff() {
        System.out.println("Post Construct Annotation");
    }
    
    @PreDestroy
    public void doMyCleanupStuff() {
        System.out.println("Pre Destroy Annotation");
    }
    
    @Override
    public String getDailyWorkout() {
        return "Practice your backhand grip";
    }
 
    @Override
    public String getDailyFortune() {
        return fortuneService.getFortune();
    }
 
}
cs

 

17번줄 ~ 25번줄 이 두 어노테이션을 사용한 예시이다

이 코드를 기반으로 실행하면 다음과 같이 결과가 나온다.

 

tennisCoach bean 에 대한 인스턴스가 생성된 후, @PostConstruct 가 실행되고, context.close 가 되고 난 후, @PreDestroy 를 실행하는 것을 볼 수 있다 (bean 이 제거되기 이전)

 

 

* Java 9 버전 이후 @PreConstruct, @PostDestroy 어노테이션이 로드 되지 않을때

1. 아래의 사이트에 가서 .jar 파일을 다운 받는다

search.maven.org/remotecontent?filepath=javax/annotation/javax.annotation-api/1.3.2/javax.annotation-api-1.3.2.jar

2. 프로젝트의 lib 폴더에 다운 받은 jar 파일을 복사해 넣는다.

3. 프로젝트의 java build path 에 다운받은 jar 를 추가한다.

 

 

지금까지 어노테이션에 대한 개요를 알아봤다.

어노테이션은 위에 서술한것 보다도 훨씬 더 많은 종류가 존재한다.

다음은 xml 파일 없이 자바코드로만 스프링 환경설정을 하는 법에 대해 알아본다. 

 

 

 

- references)

1. java.beans.IntroSpector : docs.oracle.com/javase/8/docs/api/java/beans/Introspector.html#decapitalize(java.lang.String)

2. Using @Autowired : docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-autowired-annotation

3. java reflection : www.oracle.com/technical-resources/articles/java/javareflection.html

4. Qualifiers : docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-autowired-annotation-qualifiers

 

728x90