elevne's Study Note

토비의 Spring (10: Spring Annotations) 본문

Backend/Toby Spring

토비의 Spring (10: Spring Annotations)

elevne 2023. 6. 20. 12:15

이번에는 Spring 에서 자주 사용되는 편리한 애노테이션들에 대해 알아볼 수 있었다.
 
 
 
@Resource
 
@Configuration 을 사용하는 클래스 내에서 @Resource 애노테이션을 사용하기도 한다. 이는 @Autowired 와 비슷하게 필드에 빈을 주입받을 때 사용하는데, 차이점으로 @Autowired 는 필드의 타입을 기준으로 빈을 찾고 @Resource 는 필드 이름을 기준으로 빈을 찾는다.
 
 

@Resource Database embeddedDatabase;

 
 
 
@EnableTransactionManagement
 
이전 시간에도 알아보았듯이 @EnableTransactionManagement 를 사용하여 트랜잭션 AOP 를 활성화시켜줄 수 있다. 만약 이를 달아주지 않는다면 다음 네 가지 클래스를 빈으로 등록해주는 것으로 대체할 수도 있다.
 

  • InfrastructureAdvisorAutoProxyCreator
  • AnnotationTransactionAttributeSource
  • TransactionInterceptor
  • BeanFactoryTransactionAttributeSourceAdvisor

 
 
@Autowired
 
@Autowired 애노테이션을 통해 @Configuration 클래스에서 스프링 컨테이너가 생성한 빈을 클래스의 멤버 필드로 주입받기 위해 사용해왔다. 이는 자동 와이어링 기법을 이용해서 조건에 맞는 빈을 찾아 자동으로 수정자 메소드나 필드에 넣어준다. 자동 와이어링을 이용하면 컨테이너가 이름이나 타입을 기준으로 주입될 빈을 찾아주기 때문에 빈의 프로퍼티 설정을 직접 해주는 자바 코드나 XML 의 양을 많이 줄일 수 있다. 아래와 같이 Setter 에 붙여 사용 가능하다.
 
 

@Autowired
public void setDataSource(DataSource dataSource) {
	this.datasource = datasource
}

 
 
수정자 메소드의 파라미터를 확인하고 주입 가능한 타입의 빈을 모두 찾는다.
 
 
이렇게 편한 기능을 제공해주긴 하지만, 이 애노테이션은 사용을 지양하는 편이 좋다고 한다. 또, 사용할 것이라면 필드 주입보다는 생성자 주입 방식을 택해야한다고 한다. 순환참조 문제 때문이다. 순환참조가 발생할 경우, 컴파일 타임에는 에러가 발생하지 않다가 런타임 시에 에러가 잡힌다고 한다. (그래도 생성자 주입을 사용하면 의존관계가 처음에서 벗어나지 않게되고, final 키워드를 사용하여 컴파일 오류를 확인할 수 있긴하다)
 
 
 
@Component
 
@Component 애노테이션은 스프링이 애노테이션에 담긴 메타 정보를 이용하기 시작했을 때, @Autowired 와 함께 소개된 대표적인 애노테이션이다. @Component 는 클래스에 부여되어, 이것이 붙은 클래스는 빈 스캐너를 통해 자동으로 빈으로 등록된다. 이러한 @Component 애노테이션이 달리 클래스를 자동으로 찾아서 빈을 등록해주게 하려면 빈 스캔 기능을 사용하겠다는 애노테이션 정의도 필요하다. 프로젝트 내의 모든 클래스 패스를 다 뒤져 @Component 애노테이션이 달린 클래스를 찾는 것은 부담이 많이 가는 작업이다. 그래서, 특정 패키지 아래에서만 찾도록 기준이 되는 패키지를 지정해줄 필요가 있는데, 이 때 사용되는 애노테이션이 @ComponentScan 이다.
 
 
애노테이션이 빈 스캔을 통해 자동등록 대상으로 인식되게 하려면 애노테이션 정의에 @Component 를 메타 애노테이션으로 붙여주면 된다. 책에서는 SNS 서비스에 접속하는 기능을 제공하는 빈에 AOP 포인트컷을 지정할 수 있도록 구분이 필요할 때 @SnsConnector 이라는 애노테이션을 만드는 경우를 예시로 든다. @SnsConnector 이라는 애노테이션을 정의할 때 메타 애노테이션으로 @Component 를 부여해주면 클래스마다 @Component 를 따로 붙여주지 않아도 자동 빈 등록 대상으로 만들 수 있다.
 
 

@Component
public @interface SnsConnector {
}

 
 
 
@Repository, @Service
 
스프링은 자동등록 대상으로 만들 때 사용할 수 있는 @Repository, @Service 애노테이션을 제공한다. @Component 를 사용해도 무방하긴 하지만, 스프링은 위 둘을 사용하는 것을 권장한다.
 
 
 
@Import
 
이는 @Configuration 클래스에서 사용되며, 다른 설정 클래스를 현재 클래스로 가져오는데 사용된다.
 
 

@Configuration
@Import(SqlServiceContext.class)
public class AppContext {
    ...

 
 
 
@Profile, @ActiveProfiles
 
환경에 따라서 빈 설정정보가 달라져야 하는 경우에 파일을 여러 개로 쪼개고 조합하는 등의 번거로운 방법 대신, 간단히 설정정보를 구성할 수 있는 방법을 제공한다. 실행환경에 따라 빈 구성이 달라지는 내용을 프로파일로 정의해서 만들어두고, 실행 시점에 어떤 프로파일의 빈 설정을 사용할지 지정하는 것이다.
 
 
프로파일은 간단한 이름과 빈 설정으로 구성된다. 아래 코드와 같이 @Profile 애노테이션을 클래스 레벨에 부여하고 프로파일 이름을 넣어주면 된다.
 
 

@Configuration
@Profile("test")
public class TestAppContext {
	...

 
 
프로파일을 적용하면 모든 설정 클래스를 부담 없이 메인 설정 클래스에서 @Import 해도 된다는 장점이 있다. Test 클래스에서 @ContextConfiguration 내에 메인 설정 클래스 하나만 적어줘도 되는 것이다. 하지만, 이 설정만으로는 부족하다. @Profile 이 붙은 설정 클래스는 @Import 로 가져오든 @ContextConfiguration 에 직접 명시하든 상관없이 현재 컨테이너의 활성 프로파일 목록 자신의 프로파일 이름이 붙어있지 않으면 무시된다. 활성 프로파일 목록을 지정해주는 것은 @ActiveProfiles 를 사용하면 된다.
 
 

@RunWith(SpringJUnit4ClassRunner.class)
@ActiveProfiles("test")
@ContextConfiguration(classes=AppContext.class)
public class UserServiceTest {
	...

 
 
 
@PropertySource
 
DB 연결정보는 환경에 따라 다르게 설정될 수 있어야하고, 또 같은 종류의 환경이더라도 필요에 따라 손쉽게 변경될 수 있어야한다. 그래서 이런 외부 서비스 연결에 필요한 정보는 자바 클래스에서 제거하고 손쉽게 편집할 수 있고 빌드 작업이 필요없는 XML 이나 프로퍼티 파일 같은 텍스트 파일에 저장해두는 편이 좋다.
 
 
프로퍼티에 들어갈 DB 연결정보는 텍스트로 된 이름과 값의 쌍으로 구성되면 된다.
 

database.properties

db.driverClass = com.mysql.jdbc.Driver
db.url = jdbc:mysql://localhost:3306/spring
db.username = root
db.password = 1234

 
 
@PropertySource 를 사용하여 database.properties 파일의 내용을 사용할 수 있도록 한다.
 
 

@Configuration
@EnableTransactionManagement
@ComponentScan(basePackages = "org.example")
@PropertySource("/database.properties")
public class UserConfig {

    @Autowired
    Environment env;
    @Bean
    public DataSource dataSource() throws InstantiationException, IllegalAccessException {
        Class<? extends Driver> driver;
        try {
            driver = (Class<? extends  java.sql.Driver>) Class.forName(env.getProperty("db.driverClass"));
        } catch (ClassNotFoundException e) { throw new RuntimeException(e); }
        DataSource ds = new SimpleDriverDataSource(
                driver.newInstance(),
                env.getProperty("db.url"),
                env.getProperty("db.username"),
                env.getProperty("db.password")
        );
        return ds;
    }

 
 
@PropertySource 로 등록한 리소스로부터 가져오는 프로퍼티 값은 컨테이너가 관리하는 Environment 타입의 환경 오브젝트에 저장된다. 이 또한 @Autowired 를 통해 위처럼 필드로 주입받을 수 있는 것이다. 이 오브젝트에 get 메소드를 통해서 원하는 값을 불러온다.
 
 
이 방법 말고도 프로퍼티 값을 직접 DI 받는 방법도 있다. @Value 애노테이션을 사용하는 것이다. @Value 는 값을 주입받을 때 사용된다. 컨테이너가 제공하는 프로퍼티 값을 주입받을 필드를 선언하고, 앞에 @Value 애노테이션을 붙여준다. 그리고 @Value 에는 프로퍼티 이름을 "${}" 안에 넣은 문자열을 디폴트 엘리먼트 값으로 지정한다.
 
 

@Value("${db.driverClass}") Class<? extends Driver> driverClass;
@Value("${db.url}") String url;
@Value("${db.username}") String username;
@Value("${db.password}") String password;
@Bean
public DataSource dataSourceTest() throws InstantiationException, IllegalAccessException {
    Driver driver = (Driver) (driverClass).newInstance();
    DataSource ds = new SimpleDriverDataSource(
            driver,
            url,
            username,
            password
    );
    return ds;
}

 
 
@Value 를 이용하면 driverClass 와 같이 문자열을 그대로 사용하지 않고 타입 변환이 필요한 프로퍼티를 스프링이 알아서 처리해줄 수 있다는 장점이 있다.
 
 
또, 빈에서 모듈화된 빈 설정을 가져올 때 사용하는 @Import 를 다른 애노테이션으로 대체할 수 있는 방법을 제공한다. 아래와 같이 빈 설정 클래스 값을 메타 애노테이션으로 넣어서 애노테이션을 만들어준다.
 
 

@Import(TransactionManagementConfigurationSelector.class)
public @interface EnableTransactionManagement {
	...

 
 
위와 같이 사용하면 의미도 더 잘 드러나고 깔끔해진다.



참고) 스프링 빈 등록 시에는 아래 이미지를 참고하여 작성할 수 있다.



 
 
 
 
 
 
 
Reference:
토비의 Spring

'Backend > Toby Spring' 카테고리의 다른 글

토비의 Spring 2 (1: )  (0) 2023.08.04
토비의 Spring (9: AOP(5))  (0) 2023.06.13
토비의 Spring (9: AOP(4))  (0) 2023.06.09
토비의 Spring (9: AOP(3))  (0) 2023.06.08
토비의 Spring (9: AOP(2))  (0) 2023.06.07