티스토리 뷰
스프링 3.1의 DI
어노테이션의 메타정보 활용
- 장점
- 다양한 메타 정보를 얻을 수 있음
- XML 보다 작성 편리 (IDE 도움)
- 단점
- XML 과 달리 변경 사항이 있을 때마다 새로 컴파일해줘야 함
스프링 3.1 이후 DI 3요소
- 핵심 로직을 담은 자바 코드
- DI 프레임워크
- DI를 위한 메타데이터로서의 자바 코드 (어노테이션)
자바 코드를 이용한 빈 설정
어노테이션과 자바코드로 XML 대체
config 파일
@Configuration
@ImportResource("/test-applicationContext.xml")
public class TestApplicationContext {
}
- 기존 xml 파일과 호환을 위해
@ImportResource()
를 붙임
의 전환
- @Bean
- @Configuration 이 붙은 DI 설정용 클래스에서 주로 사용되는 것으로, 메소드를 이용해서 빈 오브젝트의 생성과 의존관계 주입을 직접 자바 코드로 작성할 수 있게 해준다.
- 의 id 값 -> 메소드의 이름
- 의 class -> 생성할 빈 오브젝트의 클래스
- 변수 타입 : 구현 클래스 타입으로 (?)
- Q. 생성자 DI 를 사용하면 인터페이스 타입으로 해도 되지 않나?
@Bean
public PlatformTransactionManager transactionManager() {
//책에서는 DataSourceTransactionManager tm = new DataSourceTransactionManager();
PlatformTransactionManager transactionManager = new DataSourceTransactionManager(dataSource());
return transactionManager;
}
XML parent 속성
- @Bean 메소드로 전환할 때는 부모 bean 설정을 참고해서 프로퍼티 값을 모두 넣어줘야 함
- 자바 코드에서 inner 클래스를 참조할 때는 패키지가 다르면 public 으로 접근 제한자를 바꿔줘야 함을 주의.
public static class TestUserService extends UserServiceImpl
@Resource
- @Autowired 와 유사하게 필드에 빈을 주입받을 때 사용
- @Autowired
- 필드 타입을 기준으로 빈을 탐색
- @Resource
- 필드 이름을 기준으로 탐색
XML 전용 태그 전환
내장형 DB 생성 태그
- 내장형 DB 생성
- 초기화 스크립트 실행
- DataSource 타입의 DB 커넥션 오브젝트를 빈으로 등록
위 로직을 자바 코드로 옮긴다.
@Bean
public DataSource embeddedDatabase() {
return new EmbeddedDatabaseBuilder()
.setName("embeddedDatabase")
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:springbook...")
.build();
}
트랜잭션 태그
- InfrastructureAdvisorAutoProxyCreator
- AnnotationTransactionAttributeSource
- TransactionInterceptor
- BeanFactoryTransactionAttributeSourceAdvisor
- 위 네 가지 클래스를 빈으로 등록 -> 매우 복잡. 자바 코드로 작성 어려움.
대안
- @EnableTransactionManagement
- 스프링은 XML에서 자주 사용되는 전용 태그를 @Enable 로 시작하는 어노테이션으로 대체할 수 있게 다양한 어노테이션을 제공.
public class TestApplicationContext {
/**
* DB연결과 트랜잭션
*/
@Bean
public DataSource dataSource() {
SimpleDriverDataSource ds = new SimpleDriverDataSource();
ds.setDriverClass(Driver.class);
ds.setUrl("jdbc:mysql://localhost/springbook?characterEncoding=UTF-8");
ds.setUsername("spring");
ds.setPassword("book");
return ds;
}
@Bean
public PlatformTransactionManager transactionManager() {
DataSourceTransactionManager tm = new DataSourceTransactionManager();
tm.setDataSource(dataSource());
return tm;
}
/**
* 애플리케이션 로직 & 테스트용 빈
*/
@Autowired SqlService sqlService;
@Bean
public UserDao userDao() {
UserDaoJdbc dao = new UserDaoJdbc();
dao.setDataSource(dataSource());
dao.setSqlService(this.sqlService);
return dao;
}
@Bean
public UserService userService() {
UserServiceImpl service = new UserServiceImpl();
service.setUserDao(userDao());
service.setMailSender(mailSender());
return service;
}
@Bean
public UserService testUserService() {
TestUserService testService = new TestUserService();
testService.setUserDao(userDao());
testService.setMailSender(mailSender());
return testService;
}
@Bean
public MailSender mailSender() {
return new DummyMailSender();
}
/**
* SQL서비스
*/
@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 EmbeddedDatabaseBuilder()
.setName("embeddedDatabase")
.setType(HSQL)
.addScript("classpath:springbook/user/sqlservice/updatable/sqlRegistrySchema.sql")
.build();
}
}
빈 스캐닝과 자동 와이어링
@Autowired
- 자동와이어링 기법을 이용해 조건에 맞는 빈을 찾아 자동으로 수정자 메소드나 필드에 넣어준다.
- 장점
- 컨테이너가 이름이나 타입을 기준으로 주입될 빈을 찾아주기 때문에 빈의 프로퍼티 설정을 직접해주는 자바 코드나 XML 의 양을 대폭 줄일 수 있다.
- 단점
- 빈 설정정보를 보고 다른 빈과 의존관계가 어떻게 맺어져 있는지 한눈에 파악하기 힘들다.
As-is
@Autowired SqlService sqlService;
@Bean
public UserDao userDao() {
UserDaoJdbc dao = new UserDaoJdbc();
dao.setDataSource(dataSource());
dao.setSqlService(this.sqlService);
return dao;
}
To-be
public class UserDaoJdbc implements UserDao {
//필드 주입
@Autowired
private SqlService sqlService;
//...
//Setter 주입
@Autowired
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
}
// @Autowired SqlService sqlService;
@Bean
public UserDao userDao() {
UserDaoJdbc dao = new UserDaoJdbc();
//dao.setDataSource(dataSource());
//dao.setSqlService(this.sqlService);
return dao;
}
@Component 를 이용한 자동 빈 등록
@Component
- 클래스에 부여
- @Component 가 붙은 클래스는 빈 스캐너를 통해 자동으로 빈으로 등록됨 (정확히는 @Component 또는 @Component를 메타 어노테이션으로 갖고 있는 어노테이션이 붙은 클래스)
userDao() 메소드 제거
@Autowired UserDao userDao;
@Bean
public UserService userService() {
UserServiceImpl service = new UserServiceImpl();
service.setUserDao(this.userDao);
service.setMailSender(mailSender());
return service;
}
@Bean
public UserService testUserService() {
TestUserService testService = new TestUserService();
testService.setUserDao(this.userDao);
testService.setMailSender(mailSender());
return testService;
}
@Component
public class UserDaoJdbc implements UserDao {
//...
}
@Configuration
@EnableTransactionManagement
@ComponentScan(basePackages = "com.example.tobyspring")
public class TestApplicationContext {
//...
}
- @Component 어노테이션을 사용하려면 빈 스캔 기능을 사용하겠다는 어노테이션 정의 필요. -> @ComponentScan
- 모든 클래스패스를 다 뒤져서 @Component 가 붙은 클래스를 찾는 것은 부담이 많이 가는 작업이므로 basePackages를 지정
- 빈의 아이디는 클래스 이름의 첫 글자를 소문자로 바꿔서 사용.
- 의존관계를 담은 프로퍼티를 따로 지정할 방법이 없으므로 @Autowired 와 같은 자동 와이어링 방식을 함께 적용
- 빈 이름을 변경하고 싶다면 어노테이션에 이름을 넣어준다.
- e.g. @Component(“userDao”)
userServiceImpl 에 @Component 적용
@Component
public class UserServiceImpl implements UserService {
// ...
}
- NoSuchBeanDefinitionException 발생
- UserService type bean이 중복 -> userServiceImpl, testUserService
- 해결 방법
- UserServiceImpl의 빈 이름 수정
@Service("userService")
public class UserServiceImpl implements UserService {
// ...
}
컨텍스트 분리와 @Import
- 개선할 점
- 어플리케이션이 동작하는데 필요한 DI 정보와 테스트를 수행하기 위한 DI 정보 혼재 -> 성격이 다른 DI 정보 분리
테스트용 컨텍스트 분리
- 테스트에서만 필요한 빈 추출
- testUserService
- mailSender
@Configuration
public class TestAppContext {
// Q. 생성자 주입으로는 간단하게 만들 수 없나..?
@Bean
public UserService testUserService() {
TestUserService testService = new TestUserService();
return testService;
}
@Bean
public MailSender mailSender() {
MailSender mailSender = new DummyMailSender();
return mailSender;
}
}
- TestUserService 클래스에 @Component 를 붙여서 빈 자동등록을 할 수도 있겠지만
- 어플리케이션을 위한 클래스, 테스트를 위한 클래스가 같은 패키지에 있어서 기준 패키지 설정 어려움
- 테스트용 빈은 수동등록을 통해 빈 등록 정보를 쉽게 알 수 있게 하는 것이 좋음
@Import
- SQL Service
- 다른 빈들과 달리 독립적으로 개발되거나 변경될 가능성이 높음 -> 별도의 설정정보로 관리
@Configuration
public class SqlServiceContext {
@Bean
public SqlService sqlService() {
SqlService sqlService = new OxmSqlService(unmarshaller(), sqlRegistry());
return sqlService;
}
@Bean
public SqlRegistry sqlRegistry() {
SqlRegistry sqlRegistry = new EmbeddedDbSqlRegistry(embeddedDatabase());
return sqlRegistry;
}
@Bean
public Unmarshaller unmarshaller() {
Unmarshaller unmarshaller = new Jaxb2Marshaller();
return unmarshaller;
}
@Bean
public DataSource embeddedDatabase() {
return new EmbeddedDatabaseBuilder()
.setName("embeddedDatabase")
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:springbook...")
.build();
}
}
- 위와 같이 Context 가 추가될 때마다 테스트용 설정정보 @ContextConfiguration 에 classes 내용을 수정해야 할까? -> No. 테스트용 설정정보는 어플리케이션 설정정보와 깔끔하게 분리되는 편이 낫다.
- 반면 SqlServiceContext 는 AppContext 는 긴밀하게 결합시키는 것이 나음.
- @Import 이용해서 SqlServiceContext 설정정보를 가져오도록 수정
@Configuration
@EnableTransactionManagement
@ComponentScan(basePackages = "com.example.tobyspring")
@Import(SqlServiceContext.class)
public class AppContext {
//...
}
Profile
- MailSender
- 운영환경 : JavaMailSenderImpl
- 테스트 : DummyMailSender
- 두 개의 빈 충돌
- 문제 해결
- 운영 환경에서만 필요한 빈 설정 클래스 추가
@Configuration
@Profile("production")
public class ProductionAppContext {
@Bean
public MailSender mailSender() {
JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
mailSender.setHost("localhost");
return mailSender;
}
}
@Configuration
@Profile("test")
public class TestAppContext {
}
@Configuration
@EnableTransactionManagement
@ComponentScan(basePackages = "com.example.tobyspring")
@Import({SqlServiceContext.class, TestAppContext.class, ProductionAppContext.class})
public class AppContext {
}
@ExtendWith(SpringExtension.class)
@ActiveProfiles("test")
@ContextConfiguration(classes = AppContext.class)
class UserServiceTest {
- @ActiveProfiles(“test”) 를 통해 test profile이 지정된 testAppContext 의 빈 설정은 포함되고, ProductionAppContext의 빈 설정은 production profile로 선언되어 있으므로 무시된다.
중첩 클래스를 이용한 프로파일 적용
- Profile 마다 빈 구성이나 구현 클래스에 어떤 차이가 있는지 한 눈에 비교하기 어려움.
- 프로파일에 따라 분리했던 설정 정보를 하나의 파일로 모아보자.
@Configuration
@EnableTransactionManagement
@ComponentScan(basePackages = "com.example.tobyspring")
@Import({SqlServiceContext.class})
public class AppContext {
@Resource
DataSource embeddedDatabase;
@Bean
public UserDao userDao() {
UserDao userDao = new UserDaoJdbc(dataSource(), sqlService());
return userDao;
}
@Bean
public DataSource dataSource() {
SimpleDriverDataSource dataSource = new SimpleDriverDataSource();
dataSource.setDriverClass(Driver.class);
dataSource.setUrl("jdbc:mysql://....");
dataSource.setUsername("spring");
dataSource.setPassword("book");
return dataSource;
}
@Bean
public UserService userService() {
UserService userService = new UserServiceImpl(userDao(), mailSender());
return userService;
}
@Bean
public PlatformTransactionManager transactionManager() {
PlatformTransactionManager transactionManager = new DataSourceTransactionManager(dataSource());
return transactionManager;
}
@Configuration
public static class ProductionAppContext {
@Bean
public MailSender mailSender() {
JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
mailSender.setHost("localhost");
return mailSender;
}
}
@Configuration
@Profile("test")
public static class TestAppContext {
@Bean
public UserService testUserService() {
TestUserService testService = new TestUserService(this.userDao, mailSender());
return testService;
}
@Bean
public MailSender mailSender() {
MailSender mailSender = new JavaMailSenderImpl();
return mailSender;
}
}
}
- @Import 에 ProductionAppContext.class, TestAppContext.class 생략 가능.
- static 중첩 클래스로 넣은 @Configuration 클래스는 스프링이 자동으로 포함해준다.
프로퍼티 소스
- DB 연결정보와 같은 설정 정보를 xml 이나 프로퍼티 파일과 같은 텍스트 파일에 저장
- database.properties
db.driverClass=com.mysql.jdbc.Driver
db.url=jdbc:mysql://localhost/springbook?characterEncoding=UTF-8
db.username=spring
db.password=book
@Configuration
@EnableTransactionManagement
@ComponentScan(basePackages = "com.example.tobyspring")
@Import(SqlServiceContext.class)
@PropertySource("/database.properties")
public class AppContext {
@Bean
public DataSource dataSource() {
SimpleDriverDataSource dataSource = new SimpleDriverDataSource();
try {
dataSource.setDriverClass((Class<? extends Driver>)) Class.forName(env.getProperty("db.driverClass"));
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
dataSource.setUrl(env.getProperty("db.url"));
//...
return dataSource;
}
}
- @PropertySource 로 등록한 리소스로부터 가져오는 프로퍼티 값은 컨테이너가 관리하는 Environment 타입의 환경 오브젝트에 저장됨.
- 이것으로 DB 연결정보는 설정 클래스로부터 완전히 분리됨.
PropertySourcesPlaceholderConfigurer
- @Value 를 이용해 Environment 오브젝트 대신 프로퍼티 값을 직접 DI 받는 방법
@Configuration
@EnableTransactionManagement
@ComponentScan(basePackages = "com.example.tobyspring")
@Import(SqlServiceContext.class)
@PropertySource("/database.properties")
public class AppContext {
@Value("${db.driverClass}") Class<? extends Driver> driverClass;
@Value("${db.url") String url;
@Bean
public DataSource dataSource() {
SimpleDriverDataSource dataSource = new SimpleDriverDataSource();
dataSource.setDriverClass(this.driverClass);
dataSource.setUrl(this.url);
return dataSource;
}
//빈 후처리기로 사용되는 빈. 반드시 static 메소드로 선언.
@Bean
public static PropertySourcesPlaceholderConfigurer placeholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
- 장점
- 타입 변환을 스프링이 알아서 처리
- 단점
- dataSource 빈에서만 사용하는 프로퍼티를 위해 클래스에 필드를 선언해야 함.
빈 설정의 재사용과 @Enable*
- 개선사항
- OxmSqlReader 내 sqlmap.xml 파일 위치 지정
- UserDao.class 에 종속적
- OxmSqlReader 내 sqlmap.xml 파일 위치 지정
private Resource sqlmap = new ClassPathResource("sqlmap.xml", UserDao.class);
- 인터페이스를 정의해서 의존성을 제거
public interface SqlMapConfig {
Resource getSqlMapResource();
}
public class UserSqlMapConfig implements SqlMapConfig{
@Override
public Resource getSqlMapResource() {
return new ClassPathResource("sqlmap.xml", UserDao.class);
}
}
public class AppContext {
@Bean
public SqlMapConfig sqlMapConfig() {
return new UserSqlMapConfig();
}
//...
}
- 빈 설정정보를 위해 새로운 클래스 UserSqlMapConfig 를 추가한 것이 아쉽다. -> 간결하게 만들고 싶다.
- AppContext 도 빈으로 취급되니 AppContext 가 SqlMapConfig 인터페이스를 구현하도록 만든다. -> 이후 SqlServiceContext 에서 SqlMapConfig 를 주입 받을 때 AppContext 를 빈으로 찾아낼 수 있음
public class AppContext implements SqlMapConfig{
@Override
public org.springframework.core.io.Resource getSqlMapResource() {
return new ClassPathResource("sqlmap.xml", UserDao.class);
}
}
@Enable* 어노테이션
- @Import(SqlServiceContext.class) 를 다른 이름의 어노테이션으로 대체
- SQL Service 를 사용하겠다는 의미가 더 잘 드러나고 깔끔하다.
@Import(value = SqlServiceContext.class)
public @interface EnableSqlService {
}
@Configuration
@EnableTransactionManagement
@ComponentScan(basePackages = "com.example.tobyspring")
@EnableSqlService
@PropertySource("/database.properties")
public class AppContext implements SqlMapConfig{
}
- 추가로, @EnableSqlService 에 엘리먼트를 넣어서 sqlmap.xml 의 위치를 담도록 수정할 수도 있다.
- @EnableSqlService(“classpath:/springbook/user/sqlmap.xml”) -> vol.2 에서 다룸.
'SPRING' 카테고리의 다른 글
토비의 스프링 11. 스프링 프로젝트 시작하기 (0) | 2022.09.18 |
---|---|
토비의 스프링 10. 스프링이란 무엇인가 (0) | 2022.09.18 |
토비의 스프링 8. 스프링 핵심 기술의 응용 (1) (0) | 2022.09.18 |
토비의 스프링 7. AOP (2) (0) | 2022.09.18 |
토비의 스프링 6. AOP (1) (0) | 2022.05.27 |
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
링크
TAG
- 예외처리
- AOP
- 자바
- 프록시패턴
- 메서드레퍼런스
- 프로그래머스
- 코딩테스트
- provider
- 토비의봄TV
- c++
- ec2
- 카카오
- 스프링
- SOLID
- 템플릿콜백
- 프록시
- 코테
- 자바스터디
- 디자인패턴
- 객체지향
- gracefulshutdown
- 토비
- 서비스추상화
- 토비의스프링
- 백기선
- OOP
- BOJ
- java
- 김영한
- 데코레이터패턴
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
글 보관함