Framework & Library/Spring & Egov

7.6.2 빈 스캐닝과 자동 와이어링 / 7.6.3 컨텍스트 분리와 @Import

뒹굴거리는프로도 2018. 7. 19. 14:32
반응형

토비의 스프링 3.1




7.6 스프링 3.1의 DI 



7.6.2 빈 스캐닝과 자동 와이어링



- @Autowired를 이용한 자동와이어링


: 지금까지는 스프링 컨테이너가 생성한 빈을 클래스의 멤버 필드로 주입받기 위해, @Autowired 를 사용했다. 자동 와이어링을 사용하면, 컨테이너가 이름이나 타입을 기준으로 주입될 빈을 찾아주기 때문에, 자바 코드나 xml 양을 대폭 줄일 수 있다. 

 컨테이너가 자동으로 주입할 빈을 결정하기 어려운 경우도 있다. 이럴 땐 직접 프로퍼티에 주입할 대상을 지정하는 방법을 병행하면 된다. 


1
2
dao.setDataSource(dataSource());
dao.setSqlService(this.sqlService);
cs

현재 TestApplicationContext의 userDao() 메소드에서는 다음과 같이 수정자 메소드를 호출해서 두개의 빈 오브젝트를 직접 주입해준다. 하지만 자동 와이어링을 이용해 dataSource 빈을 넣어주는 소스를 수정해보자.

스프링은 @Autowired가 붙은 수정자 메소드가 있으면, 파라미터 타입을 보고 주입가능한 타입의 빈을 찾는다. 만약 주입 가능한 타입 빈이 2개라면, 수정자 메소드의 프로퍼티와 동일한 이름의 빈이 있는지 찾는다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class UserDaoJdbc implements UserDao{
 
    @Autowired
    public void setDataSource(DataSource dataSource){
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    // 이 수정자 메소드를 없애고, 필드에 @Autowired를 적용하는 것은 불가능하다.
    // jdbcTemplate를 생성해서 저장해주기 때문이다.
    }
  
 
    @Autowired
    private SqlService sqlService;
    // 이 때 필드의 접근 제한자가 private여도 무방하다.
    // 스프링은 리플렉션 API를 이용해 제약조건을 우회해서 값을 넣어준다.
 
    public void setSqlService(SqlService sqlService){
        this.sqlService = sqlService;
    }
}
 
cs


userDao 빈을 정의한 userDao() 메소드에서 더 이상 수정자 메소드를 호출해서 빈을 넣어주지 않아도 된다.

 

1
2
3
4
@Bean 
public UserDao userDao(){
    return new UserDaoJdbc();
}
cs



 setSqlService() 메소드는 생략해도 좋다. 필드에 직접 값을 넣을 수 있다면 수정자 메소드는 없어도 된다. 하지만 @Autowired를 필드에 직접 부여했다고 메소드를 생략하면 안 되는 경우가 있다. 스프링과 무관하게 직접 오브젝트를 생성하고 다른 오브젝트를 주입해서 테스트 하는 순수한 단위 테스트를 만드는 경우에는 수정자 메소드가 필요하다. 


@Autowired와 같은 자동와이어링은 DI 관련 코드를 줄일 수 있다는 장점이 있으나, 다른 빈과의 의존 관계를 한눈에 파악하기 힘들다는 단점이 있다.





- @Component를 이용한 자동 빈 등록

: @Component는 클래스에 부여된다. 또한 빈 스캐너를 통해 자동으로 빈으로 등록된다.


이번엔 아예 UserDao()를 제거해보자. 이러면 에러가 나는데, userDao() 메소드를 호출해서 userDao() 빈을 가져오는 코드가 있기 때문이다. 간단히 @Autowired를 통해 빈을 참조하게 하면 된다.


userDao()를 지웠으니, 자동 빈 등록 대상이 될 UserDaoJdbc 클래스에 @Component 애노테이션을 넣는다. 

@Component는 빈으로 등록될 후보 클래스에 붙여주는 일종의 마커이다.


1
2
3
@Component
public class UserDaoJdbc implements UserDao(){
    
cs


@Component 애노테이션이 달린 클래스를 자동으로 찾아서 빈을 등록해주게 하려면, 빈 스캔 기능을 사용하겠다는 애노테이션 정의가 필요하다. 특정 패키지 아래에서만 찾도록 기준이 되는 패키지를 지정해 줄 필요가 있다. 이때 @ComponentScan을 쓴다.

1
2
3
4
5
6
7
@Configuration
@EnableTransactionManagement
@ComponentScan(basePackages="springbook.user")
public class TestApplicationContext{
 
 
 
cs

 이렇게 자동 빈 등록을 이용하는 경우 빈의 의존관계를 담은 프로퍼티를 따로 지정할 방법이 없다. 그래서 @Autowired와 같은 자동 와이어링 방식을 적용해야 한다. 근데 이렇게 빈이 자동으로 등록될 때 빈의 아이디는 클래스 이름을 따라간다. (첫 글자는 소문자) 이름은 userDaoJdbc 빈이 될 것이다. 

 아이디가 userDaoJdbc로 바뀌었지만, @Autowired를 이용해 빈을 주입받는다. 따라서 UserDao 인터페이스를 구현하고 있으니 @Autowired에 의해 자동 와이어링 대상이 된다. 만약 같은 타입의 빈이 2개 이상일 경우를 대비하여 애노테이션에 이름을 넣어줄 수 있다.

1
@Component("userDao")
cs


또한 @Component 애노테이션을 메타 애노테이션으로 갖고 있는 애노테이션도 사용할 수 있다고 했다. 애노테이션은 @interface 키워드를 이용해 정의한다. @Component 애노테이션은 다음과 같이 정의되어 있다.

1
2
3
public @interface Component{
    
}
cs



 AOP 에서 포인트컷을 작성할 때도 애노테이션을 사용할 수 있다. @Transactional이 대표적인 예다. 여러 개의 애노테이션에 공통적인 속성을 부여하려면 메타 애노테이션을 이용한다. 메타 애노테이션은 애노테이션의 정의에 부여된 애노테이션을 말한다. 애노테이션이 빈 스캔을 통해 자동등록 대상으로 인식되게 하려면, 애노테이션 정의에 @Component를 메타 애노테이션으로 붙여주면 된다. 


@Component 메타 애노테이션을 가진 애노테이션 정의

1
2
3
4
@Component
public @interface SnsConnector{
    ...
}
cs

이제 @SnsConnector를 주면 자동 빈 등록 대상이 된다.

1
2
@SnsConnector
public class FacebookConnector{
cs

스프링은 데이터 엑세스 서비스를 제공하는 DAO빈을, 자동등록 대상으로 만들 때 사용할 수 있게

@Repository 애노테이션을 제공하며 권장한다.

1
2
@Repository
public class UserDaoJdbc implements UserDao{
cs




이제 userService 빈에도 자동 와이어링과 자동 빈 등록을 적용해보자. 그냥 @Component만 사용하면 문제가 생긴다.

@Autowired 필드 이름을 userServiceImpl로 바꾸거나, @Component의 빈 아이디를 userService라고 지정하는 해결방법이 있다.

여기서는 @Component 보다 @Service를 쓴다. 비즈니스 로직을 담고 있는 서비스 계층의 빈을 구분하기 위해 사용되는 것이다.

서비스 계층은 트랜젝션 경계가 되는 곳이라 @Transactional이 함께 사용되는 경우가 많다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Autowired UserService userService;
@Autowired UserSerivce testUserService;
// 같은 타입 빈이 2개 이상이며, 다른 빈 아이디로
// @Component를 사용해서 UserServiceImpl을 등록하면 문제가 생긴다.
 
// @Component
@Service("userService")
public class UserServiceImpl implements UserService{
 
    ...
    @Autowired
    private UserDao userDao;
 
    @Autowired
    private MailSender mailSender;
}
cs



dataSource나 transactionManager 빈은 스프링이 제공하는 클래스를 사용하는 것이기 때문에 소스코드에 @Component나 @Autowired를 적용할 수 없다.





7.6.3 컨텍스트 분리와 @Import


: 이 장에서는 성격이 다른 DI 정보를 분리한다. (애플리케이션이 동작하는 데 필요한 정보와 테스트용 정보)

testUserService 빈은 테스트에서만 사용되며, mailSender 빈은 운영중에 사용되면 안된다.


- 테스트용 컨텍스트 분리

: DI 설정정보를 분리하려면 DI 설정 클래스를 추가하고, 관련된 빈 설정 애노테이션, 필드, 메소드를 옮기면 된다.  TestApplicationContext 클래스의 이름을 Appcontext라고 바꾸자. 이것은 테스트 정보를 분리하고 남은 애플리케이션의 핵심 DI 정보를 남겨둘 클래스가 된다. 테스트용 빈 정보는 분리해서 TestAppContext라고 이름 짓는다.


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
@Configuration
public class TestAppContext{
 
    /*@Autowired UserDao userDao;
    제거 가능
 
    @Bean 
    public UserService testUserService(){
        TestUserSerivce testService = new TestUserService();
        testService.setUserDao(this.userDao);
        testService.setMailSender(mailSender());
        return testService;
    }*/
 
    @Bean
    public UserService testUserService(){
        return new testUserService();
    }
 
    @Bean
    public MailSender mailSender(){
        return new DummyMailSender();
    }
}
 
cs


이제 운영 시스템에서 애플리케이션이 동작할 때는 AppContext만 참조하고, 테스트에서는 Appcontext와 TestAppContext  두 개의 DI 정보를 사용하게 해줄 것이다.

1
2
3
4
5
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={TestAppContext.class, AppContext.class})
//테스트 코드의 DI 정보용 클래스 
public class UserDaoTest{
 
cs




-@Import


: SQL 서비스와 관련된 빈들을 분리해보자. SqlService의 구현 클래스와 이를 지원하는 보조 빈들은, 다른 애플리케이션을 구성하는 빈과 달리 독립적으로 개발되거나 변경될 가능성이 높다. SqlServiceContext라는 이름으로 @Configuration 클래스를 하나 더 만들고 SqlService 관련 빈을 옮겨보자.

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
@Configuration
public class SqlServiceContext{
 
    @Bean
    public SqlService sqlService(){
        OxmSqlService sqlService = new OxmSqlService();
        sqlService.setUnmarshaller(unmarshaller());
        sqlService.setSqlRegistry(sqlRegistry());
        return sqlService;
    }
 
    @Bean 
    public SqlRegistry sqlRegistry(){
        EmbeddedDbSqlRegistry sqlRegistry = new EmbeddedDbSqlRegistry();
        sqlRegistry.setDataSource(embeddedDatabase());
        return sqlRegistry;
    }
 
    @Bean
    public Unmarshaller unmarshaller(){
        Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
        marshaller.setContextPath("springbook.user.sqlservice.jaxb");
        return marshaller;
    }
 
    @Bean
    public DataSource embeddedDatabase(){
        return new EnbeddedDatabaseBuilder()
            .setName("embeddedDatabase")
            .setType(HSQL)
            .addScript("classpath:springbook/user/sqlservice/updatable/sqlRegistrySchema.sql")
            .build();
    } 
}
cs

 이제 DI 설정정보를 담은 클래스가 세 개가 되었다. 테스트용 설정정보는 애플리케이션 핵심 설정정보와 깔끔하게 분리되는 편이 낫다.

@ContextConfiguration의 classes를 수정하는 방법도 가능하지만, 애플리케이션 설정정보의 중심이 되는 AppContext과 긴밀하게 연결해주자.

 AppContext가 메인 설정정보가 되고, SqlServiceContext는 AppContext에 포함되는 보조 설정정보로 사용한다. XML의 경우 @ImportResource를 사용하지만, 자바 클래스로 된 설정 정보를 가져올 때는 @Import를 이용한다.


1
2
3
4
5
@Configuration
@EnableTransactionManagement
@ComponentScan(basePackage="springbook.user")
@Import(SqlServiceContext.class)
public class AppContext{
cs












반응형