[Spring 3.1 @Enable] 2. @Import와 ImportAware

이번에는 @Import를 사용해서 빈 설정을 추가하는 방법입니다.

이렇게 @Import 애노테이션을 사용해서 다른 자바 설정을 추가할 수 있습니다. 이렇게하면, 상속을 사용하지 않아도 되지만, 상속했을 때처럼 무언가를 변경할 방법이 보이질 않습니다. 그리고 @Import에 사용할 클래스 이름을 외워야 한다는 문제는 여전히 남게되죠.

일단 @Import를 메타 애노테이션으로 사용하는 @Enable*애노테이션을 만들 수 있습니다.

이렇게 @EnableHello라는 애노테이션을 만들고, @Import로 HelloConfig.java설정을 여기에 추가해뒀습니다. 그럼 이제, AppConfig는 @Import를 직접 사용하는게 아니라, @EnableHello를 이용해서 간접적으로 사용할 수 있게 됩니다.

이제 클래스 이름을 몰라도 되긴하지만, 아직도 확장할 방법은 보이질 않죠. @EnableHello 애노테이션에 속성을 추가하고, 그 애노테이션 정보를 활용해서 빈 정보를 조작할 수 있습니다.

먼저 @EnableHello를 다음과 같이 바꿔보죠.

이렇게 애노테이션에 속성을 추가하면, 이젠 반드시 @EnableHello를 사용할 때 name 값을 줘야 합니다. (물론, default 키워드로 기본값을 설정하면 강제적으로 설정하지 않게 할 수도 있죠.)

그럼 그리고 이제 HelloConfig가 ImportAware라는 인터페이스를 구현하게합니다.

이렇게 ImportAware 인터페이스의 구현 메서드에서 @Import가 붙어있는 곳(여기서는 AppConfig.java)의 애노테이션 정보(@EnableHello)를 바탕으로 빈 속성을 변경하게 했습니다.

이게 끝이아니라, 이제부터 시작입니다.

만약에 지금처럼 단순하게, 빈의 내용만 바꾸는게 아니라, 특정 속성에 따라 등록되어야 하는 빈 자체가 달라진다면 어떻게 해야할까요? 지금은 HelloConfig에 선언한 모든 @Bean이 반드시 빈으로 등록되고 있는데, 특정 옵션에 따라 어떤 빈이 필요하기도 하고, 필요없기도 하다면… ㅎㅎ 문제가 점점 재밌어 집니다.

[Spring 3.1 @Enable] 1. @Configuration 상속을 사용한 확장 방법

저는 사실 오늘 토비님 발표를 두번봤습니다. 실제 발표 시간 때 한번봤고, 발표하기 전에 리허설 때 한번 먼저 봤습니다. 리허설을 보기 전에는 발표 자료를 또 먼저 봤었구요. 발표자료만 봤을 때는 꽤나 어렵게 느껴졌는데, 리허설 때 라이브 코딩을 보니까 이해하기가 더 수월했습니다. 그리고 실제 발표 시간에는 직접 토비님 코딩에 맞춰서 저도 똑같이 따라서 코딩했습니다.

어쩄든 보면서 코딩했던걸 되새기며, 까먹기 전에 후딱 기록합니다.

먼저, 별로 권장할만한 방법은 아니지만, 가장 단순한 방법은 @Configuration 클래스를 상속받는 방법입니다. 상속 받아서 오버라이딩을 하거나, 새로운 @Bean을 추가할 수 있겠습니다.

이런 클래스가 있고, 이걸 자바 설정으로 다음과 같은 빈 설정 파일을 만들었다고 가정하겠습니다.

이 빈 설정에 등록한 Hello @Bean을 다른 빈 설정에서도 재사용 및 확장하고 싶다면, 다음과 같이 단순하게 상속해서 오버라이딩 할 수 있습니다.

하지만, 다음과 같은 문제가 있죠.

  • 클래스 이름 외워야 한다.
  • 자바는 단일 상속이기 때문에 다른 설정은 확장하지 못한다.
  • 자바의 상속이 가진 모든 단점도 지니게 된다.
  • @Configuration의 @Bean은 CGLib 프록시를 사용하기 때문에 @Bean 메서드에 final을 사용해서 중요 로직이 오버라이딩되지 않도록 방지할 수가 없어서, 자칫 오버라이딩으로 인해 중요 로직까지 바뀔 수 있다.

그래서 이 방법은 가장 단순한 방법이긴 하지만, 별로 권장할만한 방법은 아닙니다. 그렇다고 쓰지 말라는 법은 없으니깐 뭐.. 흠냐..

[Spring 3.1] TestContext

스프링  3.1 TestContext 변경 내역은 표면적으로 보자면, 크게 두가지입니다.

  • @Configuration 지원
  • @ActiveProfiles 지원

사실 자바 설정은 스프링 3.0 부터 지원했지만, 사용하기 어렵던 이유가, 스프링 TestContext에서 사용하기 번거롭다는 점과, XML 네임스페이스에 해당하는 기능이 없다는 것이었는데, 스프링 3.1부터는 이런 문제가 완전 해결됐습니다.

이런 자바 설정 파일이 있을때, 다음과 같이 테스트를 작성할 수 있습니다.

여기서 주의하실 것은, @ContextConfiguration에는 location이나 classes 둘 중 하나만 사용할 수 있다는 것입니다. 따라서, 여러 종류의 설정 파일, 보통 XML 설정과 자바 설정을 혼용하시겠죠. 그밖에 프로퍼티즈 파일도 사용할 수 있지만.. 좀.. 쓰기가 ㅋ; 암튼, 그런 경우에 설정 파일 중에 하나를 진입점으로 사용해야 합니다.

XML 설정을 진입점으로 사용하려면, 자바 설정을 컴포넌트 스캔을 하거나, 직접 <bean/>을 사용해서 XML 설정에 빈으로 등록해주면 됩니다.

반대로, 자바 설정을 진입점으로 사용하려면, XML 설정을 @ImportResource로 자바 설정에 추가해주면 됩니다.

다음으로 스프링 3.1 TestContext의 프로파일 지원 기능은 스프링 3.1에 추가된 빈 프로파일링 기능을 사용할 때, 해당 프로파일을 손쉽게 테스트할 수 있는 기능입니다.

이렇게 빈 프로파일을 사용한 설정 파일이 있을 때, default 프로파일이 제대로 적용되는지, korea 라는 프로파이일 때 toby라는 id를 가진 빈이 정말 다른 걸로 바뀌는지, 다음과 같이 확인할 수 있습니다.

[Servlet 3.0 @MultipartConfig]파일 업로드가 좀 편해졌으려나…

먼저, Servlet 3.0으로 파일업로드를 해보자.

일단, 간단한 파일 업로드 폼을 만들었다.

다음은 @WebServlet@MultipartConfig를 추가해서 업로드 파일을 저장할 장소와 파일 크기, 요청의 총 크기 등을 설정할 수 있다.

여기서 핵심은, Part라는 API다. MultiPart 타입의 요청에서 한 부분을 Part라고 참조할 수 있으니, 해당 요청에 파일만 업로드 했다는 가정하에, 한 파일당 하나의 Part가 생신다고고 볼 수 있겠다. 이 Part에 접근하는 방법은 두개.

  • 전체다 가져오기: req.getParts()
  • 한개만 가져오기: req.getPart(String name)

일단 괜찮아 보인다. 용량 설정과 디렉토리 설정을 할 수 있다는 것이 좋다. 이렇게 하면, 요청마다 업로드와 관련 된 설정을 유동적으로 가져가기 좋아보인다. 대신 여러번 설정해야 하면 귀찮기도 하겠지만, 커스텀 애노테이션을 만들거나, 자바 클래스 상속으로 해결할 수 있을지도 모르니깐.. (근데 이건 안해봐서 좀.. ㅋ)

시험삼아 chmox.dmg파일을 업로드해보고, Request 정보를 출력해봤다.

Name:
file
Header:
content-type
application/octet-stream
content-disposition
form-data; name=”file”; filename=”Chmox-0.3.dmg”
Size:
256905

파일 이름을 알아내려면 content-diposition 헤더 정보를 읽어서 그 중에서도 filename=”부터 “까지를 잘라내야하는데, 참 귀찮은 일이다. 그보다는 파일을 선택했을 때 뷰에서 해당 파일 이름을 input type=”text”에 자동으로 채워주고, 서버에서는 그 값을 받아서 사용하는게 더 좋겠다.

이번에는 이 기능을 스프링 3.1을 사용해서 해봤다.

http://static.springsource.org/spring/docs/3.1.x/spring-framework-reference/html/mvc.html#mvc-multipart-resolver-standard

레퍼런스를 참고했다. 일단 레퍼런스에 글로만 설명된대로 web.xml을 손봐야한다.

이렇게 하면 이제 해당 DispatcherServlet으로 들어오는 요청이 Servlet 3.0 파일 업로드 기능을 사용할 수 있게 된다.

아 그리고 빈을 하나 추가해줘야 한다. 아파치 커먼스 파일업로드를 쓸때도 하나 등록했었으니깐.. 이건 뭐 쌤쌤.

이전에는 요거 대신 아래 녀석을 등록했었다.

차이가 있다면, 파일 업로드 용량을 스프링 빈으로 설정하던걸, web.xml로 옮겨갔다는 것… 난 이전이 더 편한것 같다.

이런 고민이 생긴다.

“특정 요청마다 다른 기준으로 용량을 제한해야 한다면?”

스프링으로는 어떻게 하면 좋을까? multipartResolver는 DispatcherServlet당 하나밖에 못쓰니까.. 뭔가 URL 별로, 다른 MultipartResolver를 사용할 수 있는 기능을 가진 MultipartResolver를 만들면 될까나~?

[Spring 3.1] 자바 설정 예제

먼저, XML 설정부터 볼까나..

<bean id="userDao" class="chapter06.exercise.aop12.UserDaoJdbc">
    <property name="dataSource" ref="dataSource" />
</bean>

<tx:annotation-driven />

<bean id="userService" class="chapter06.exercise.aop12.UserServiceImpl">
    <property name="userDao" ref="userDao" />
    <property name="mailSender" ref="mailSender" />
</bean>

<bean id="testUserService"
    class="chapter06.exercise.aop12.UserServiceTest$TestUserServiceImpl"
    parent="userService" />

<bean id="transactionManager"
    class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
</bean>

<jdbc:embedded-database type="HSQL" id="dataSource">
    <jdbc:script location="member_table_1.sql" />
</jdbc:embedded-database>

<bean id="mailSender" class="chapter06.exercise.aop12.DummyMailSender" />

이 설정은 내가 강의할 때 사용하는 예제 코드에 들어있는 코드로, 토스3에 있는 코드를 기반으로 작성한 샘플 코드다. jdbc:embedded를 사용하는 등, 실습하기 손쉽게 조금 고친 부분이 있다.

이 설정을 스프링 3.1 자바 설정으로 다음과 같이 변경할 수 있다.

@Configuration
@EnableTransactionManagement
public class TestDaoConfg {
   
    @Bean
    public UserDao userDao(DataSource dataSource) {
        UserDaoJdbc userDaoJdbc = new UserDaoJdbc();
        userDaoJdbc.setDataSource(dataSource);
        return userDaoJdbc;
    }
   
    @Bean
    public UserService userService(UserDao userDao, MailSender mailSender){
        UserServiceImpl userServiceImpl = new UserServiceImpl();
        userServiceImpl.setUserDao(userDao);
        userServiceImpl.setMailSender(mailSender);
        return userServiceImpl;
    }
   
    @Bean
    public UserService testUserService(UserDao userDao, MailSender mailSender){
        TestUserServiceImpl userServiceImpl = new TestUserServiceImpl();
        userServiceImpl.setUserDao(userDao);
        userServiceImpl.setMailSender(mailSender);
        return userServiceImpl;
    }
   
    @Bean
    public MailSender mailSender(){
        return new DummyMailSender();
    }
   
    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource){
        return new DataSourceTransactionManager(dataSource);
    }
   
    @Bean(destroyMethod="shutdown")
    public DataSource dataSource(){
        EmbeddedDatabaseFactory factory = new EmbeddedDatabaseFactory();
        factory.setDatabaseName("sample");
        factory.setDatabaseType(EmbeddedDatabaseType.HSQL);
        ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
        populator.addScript(new ClassPathResource("member_table_1.sql"));
        factory.setDatabasePopulator(populator);
        return factory.getDatabase();
    }
   

}

뭐 대부분은 문제가 아닌데… jdbc 네임스페이스를 대체하는 코드는 도무지 외울 수가 없다.. XML이 더 편하게 느껴진다. 잘못해서 destroyMethod라도 설정하지 않으면 어쩔것인가…

@EnableXxx과 TestContext 연동으로 XML에서 자바 설정으로 넘어가지 위해 열심히 코딩해준 건 고맙지만.. 아직 2% 부족하게 느껴진다.