-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[8주차](이승철, 김동하, 양재승) #10
Comments
토비의 스프링 7장 정리7.5 DI를 이용해 다양한 구현 방법 적용하기운영중인 시스템에서 사용하는 정보를 실시간으로 변경하는 작업을 만들 때 가장 먼저 고려해야 할 사항은 동시성 문제이다.
public class ConcurrentHashMapSqlRegistry implements UpdatableSqlRegistry {
private Map<String, String> sqlMap = new ConcurrentHashMap<String, String>();
@Override
public String findSql(String key) throws SqlNotFoundException {
String sql = sqlMap.get(key);
if(sql == null) throw new
SqlNotFoundException(key + "를 이용해서 SQL을 찾을 수 없습니다");
else return sql;
}
@Override
public void registerSql(String key, String sql) {
sqlMap.put(key, sql);
}
@Override
public void updateSql(String key, String sql) throws SqlUpdateFailureException {
if(sqlMap.get(key) == null) {
throw new SqlUpdateFailureException(key + "에 해당하는 SQL을 찾을 수 없습니다.");
}
sqlMap.put(key, sql);
}
@Override
public void updateSql(Map<String, String> sqlmap) throws SqlUpdateFailureException {
for(Map.Entry<String, String> entry : sqlmap.entrySet()) {
updateSql(entry.getKey(), entry.getValue());
}
}
}
스프이 제공하는 내장형 DB 빌더는 EmbeddedDatabaseBuilder이다. 다음은 EmbeddedDatabaseBuilder를 사용하는 전형적인 방법이다. new EmbeddedDatabaseBuilder()
.setType(내장형DB종류)
.addScript(초기화에 사용할 DB 스크립티의 리소스)
...
.build(); EmbeddedDatabaseBuilder는 직접 빈으로 등록한다고 바로 사용할수 있는 게 아니라 적절한 메소드를 호출해주는 초기화 코드가 필요하다. public class EmbeddedDbSqlRegistry implements UpdatableSqlRegistry{
SimpleJdbcTemplate jdbc;
public void setDataSource(DataSource dataSource) {
jdbc = new SimpleJdbcTemplate(dataSource);
//DataSource를 DI 받아서 SimpleJdbcTemplate 형태로 저장해두고 사용한다.
}
@Override
public String findSql(String key) throws SqlNotFoundException {
try{
return jdbc.queryForObject("select sql_from sqlmap where key_ = ?", String.class, key);
}
catch(EmptyResultDataAccessException e) {
throw new SqlNotFoundException(key + "에 해당하는 SQL을 찾을 수 없습니다", e);
}
}
@Override
public void registerSql(String key, String sql) {
jdbc.update("insert into sqlmap(key_, sql_) values(?,?)",key, sql);
}
@Override
public void updateSql(String key, String sql) throws SqlUpdateFailureException {
// update()는 SQL 실행 결과로 영향을 받은 레코드의 개수를 리턴한다.
// 이를 이용하면 주어진 키(key)를 가진 SQL이 존재했는지를 간단히 확인할 수 있다.
int affected = jdbc.update("update sqlmap set sql_ = ? where key_ = ?", sql, key);
if(affected == 0) {
throw new SqlUpdateFailureException(key + "에 해당하는 SQL을 찾을 수 없습니다.");
}
}
}
트랜잭션의 적용은 수동 테스트 따위로 검증하기는 매우 어렵다. 특별한 예외상황이 아니라면 트랜잭션의 적용 여부가 결과에 별 영향을 주지 않기 때문이다. @Test
public void transactionalUpdate() {
checkFindResult("SQL1", "SQL2", "SQL3"); // 초기 상태를 확인한다.
Map<String, String> sqlmap = new HashMap<String, String>();
sqlmap.put("KEY1", "Modified1");
sqlmap.put("KEY9999!@#$", "Modified9999");
// 두 번째 SQL의 키를 존재하지 않는 것으로 지정한다. 이때문에 테스트는 실패하고, 롤백이 일어나는지 확인한다.
try {
sqlRegistry.updateSql(sqlmap);
fail();
}catch (SqlUpdateFailureException e) {}
checkFindResult("SQL1", "SQL2", "SQL3");
// 첫번째 SQL은 정상적으로 수행했지만 트랜잭션이 롤백되기때문에 다시 변경 이전 상태로 돌아와야 한다.
// 트랜잭션이 적용되지 않는다면 변경된 채로 남아서 테스트는 실패한다.
} 아직 트랜잭션 적용을 하지 않았기에 이 테스트는 실패한다.
7.6 스프링 3.1의 DI자바 언어의 변화와 스프링 자바 언어와 관련 기술이 다양한 변화를 겪었고, 그에따라 스프링도 꾸준히 발전하고 변신해왔다.
애노테이션이 늘어난 이유1. 핵심로직을 담은 자바코드와 IOC방식의 플레임워크, 프레임워크가 참조하는 메타정보 이 세가지로 구성하는 방식에 잘 어울린다. 2. 애플리케이션을 구성하는 많은 오브젝트와의 관계를 설정할 때 단순 자바코드로 만들어두면 불편하기에 XML로 전환을 했고 그 후 애노테이션이 등장을 했다. 애노테이션은 자바코드의 일부로 사용되기 때문에 XML과 비교했을때 여러 이점이 많다.
이 장에서는 지금까지의 예제 코드를 스프링 3.1의 DI스타일로 바꾸는 과정을 설명한다. 7.6.1 자바 코드를 이용한 빈 설정테스트 컨텍스트의 변경 @Configuration
public class TestAppicationContext{
}
// UserDaoTest.java
//...
@ContextConfiguration(classes=TestApplicationContext.class) // 이제 XML 대신 클래스에서 DI 설정을 찾는다.
public class UserDaoTest{
} 자바 클래스로 만들어진 DI설정에 XML의 설정정보를 가져올 수도 있다. @Configuration
@ImportResource("/test-applicationContext.xml") // xml 설정정보를 가져온다.
public class TestAppicationContext{
} <context:annotation-config/> 제거 XML을 쓸때는 이 태그에 의해 등록되는 빈 후처리기가 @PostConstruct 와 같은 표준 애노테이션을 인식해서 자동으로 메서드를 실행해 주었지만, <bean>의 전환 <bean> 으로 정의된 DI 정보는 @bean이 붙은 메서드와 1:1로 매핑된다. (메서드 이름이 의 id이다.) @Bean
public DataSource dataSource() {
SimpleDriverDataSource dataSource = new SimplerDriverDataSource();
dataSource.setDriverClass(Driver.class);
dataSource.setUrl("jdbc:mysql://localhost:port/....");
dataSource.setUsername("myid");
dataSource.setPassword("1q2w3e!");
return dataSource;
} XML의 프로퍼티를 이용해 자바 코드로 작성한 DI 정보를 참조할 수 있었지만, XML에 작성된 DI 정보를 자바 코드에서 참조할 수 있게 해준다. @Autowired
SqlService sqlService;
@Bean
public UserDao userDao() {
. . .
dao.setSqlService(this.sqlService);
. . .
} 전용 태그 전환 XML에는 리스트 7-96에 나온 두 개의 빈 설정만 남았다. <jdbc:embedded-database id=”embeddedDatabase” type=”HSQL”>
<jdbc:script location=”classpath:schema.sql”/> <!-- 초기화 SQL 스크립트 등록 -->
</jdbc:embedded-database>
<tx:annotation-driven /> 지금까지 전환엔 문제가 없었지만, 특수한 태그를 이용한 위 두 개의 빈은 내부에서 어떤 과정을 거쳐 빈이 만들어지는지 파악하기 어렵다. 스프링 서비스에서 사용하는 내장형 DB를 생성하는 /jdbc:embedded-database 전용 태그는 type에 지정한 내장형 DB를 생성하고 <jdbc:script> 로 지정한 스크립트로 초기화 한뒤, DataSource 타입 DB 커넥션 오브젝트를 빈으로 등록해준다. 트랜잭션 AOP를 적용하려면 수많은 빈이 필요한데, <tx:annotation-driven /> 태그는 기본적으로 아래 4가지 클래스를 빈으로 등록한다. InfrastructureAdvisorAutoProxyCreator 하지만 스프링은 @enable로 시작하는 애노테이션으로 대체할 수 있도록 애노테이션을 제공한다. 7.6.2. 빈 스캐닝과 자동 와이어링@Autowired를 이용한 자동와이어링
@Autowired
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
} setter에서 필드 그대로 넣는다면 필드에 직접 @Autowired 를 적용할 수 있다. 자동 와이어링은 DI 관련 코드를 줄일 수 있는 장점이 있지만, 다른 빈과 의존관계가 어떻게 맺어져 있는지 파악하기가 힘들다. @component 를 이용한 자동 빈 등록 클래스에 붙이면 빈 스캐너를 통해 자동으로 등록될 대상이 된다. @component로 추가되는 빈의 id는 별도로 지정이 없으면 클래스 이름의 첫글자를 소문자로 바꿔서 사용한다.
그런데 스프링에선 @component 이외의 애노테이션을 부착하여도 자동으로 빈 등록이 가능하다. 빈 스캔 검색 대상으로 만들 뿐 아니라 다른 의미의 마커로도 사용할 수 있도록 하기 위함이다. 이럴때 메타 애노테이셭을 사용한다. 메타 애노테이션메타 애노테이션이란 애노테이션의 정의에 부여된 애노테이션을 말한다. 애노테이션끼리는 상속도 안되고, 인터페이스 구현도 안되기 때문에 여러개의 애노테이션에 공통 속성을 부여하려면 메타 애노테이션을 사용한다.다음은 메타 애노테이션으로 만들어진 @SnsConnector 애노테이션이다. @Component
public @interface MyAnnotation{
. . .
} 7.6.3 컨텍스트 분리와 @import테스트용 컨텍스트 분리 지금까지 태스트용 testUserService 빈과 userService 빈을 한곳의 XML로 담아 두었다. 성격이 다른 DI 정보를 분리해보자. 기존에는 하나였던 설정을 1. 테스트애만 쓰이는 TestAppContext와 실제 앱의 동작에 쓰이는 AppContext 로 분리하고, @ContextConfiguration(classes={TestAppContext.class, AppContext.class})
public class UserDaoTest { 지금까지 만들었던 SqlService는 AppContext 내의 빈들과 구분되는 특징이 있고, 다른데에서도 충분히 쓰일 수 있기 때문에 @Configuration
public class SqlServiceContext {
@Bean
public SqlService sqlService() {
0xmSqlService sqlService = new 0xmSqlService();
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.sqlservice.jaxb");
return marshaller;
}
@Bean
public DataSource embeddedDatabase() {
return new EmbeddedDatabaseBuilder()
.setName("embeddedDatabase")
.setType(HSQL)
.addScript(
"classpath:springbook/user/sqlservice/updatable/sqlReistrySchema.sql")
.build();
}
} SQL 서비스와 관련된 빈 설정은 별도로 분리하긴 했지만 애플리케이션이 동작할 때 항상 필요한 정보다. 따라서 파일을 구분했더라도 애플리케이션 설정정보의 중심이 되는 @Configuration
@EnableTransactionManagement
@ComponentScan(basePackages="springbook.user")
@Import(SqlServiceContext.class)
public class AppContext { 7.6.4 프로파일테스트 환경과 운영 환경에서 각각 다른 빈 정의가 필요한 경우가 있다. 테스트를 실행할때 DummyMailSender 라는 테스트용 클래스를 만들어 사용했는데, 이때 사용하는 것이 @Profile과 @activeprofiles 이다. 실행환경에 따라 빈 구성이 달라지는 내용을 프로파일로 정의해서 만들어두고, 실행시점에 지정해서 사용한다. @Configuration
@Profile("test")
public class TestAppContext {
//@Profile을 지정한 TestAppContext
} @Configuration
@EnableTransactionManagement
@ComponentScan(basePackages="springbook.user")
@Import({SqlServiceContext.class, TestAppContext.class, ProductionAppContext.class})
public class AppContext {
//@Import에 모든 설정 클래스 추가
} @RunWith(SpringJUnit4ClassRunner.class)
@ActiveProfiles("test")
@ContextConfiguration(classes=AppContext.class)
public class UserServiceTest {
//활성 프로파일을 지정한 UserServiceTest 컨테이너의 빈 등록 정보 확인 정말 활성 프로파일이 제대로 적용돼서 지정한 프로파일의 빈 설정만 적용되고 나머지는 무시됐는지, 의도한 빈 정보만 적용됐는지를 알고싶다면 @Autowired
DefaultListableBeanFactory bf;
@Test
public void beans(){
for(String str : bf.getBeanDefinitionNames()){
System.out.println(str + "\t" + bf.getBean(str).getClass().getName());
}
} 스프링 컨테이너는 BeanFactory 인터페이스를 구현하고, 그 중 DefaultListableBeanFactory 구현 클래스는 대부분의 스프링 컨테이너에서 사용된다. 중첩 클래스를 이용한 프로파일 적용 프로파일 설정과, 목적이 다른 빈을 다른 클래스로 분리해 놓은것은 여전히 유효하도록 하면서, 가독성을 높이는 방법이다. @Configuration
@EnableTransactionManagement
@ComponentScan(basePackages="springbook.user")
@Import({SqlServiceContext.class,
AppContext.TestAppContext.class, AppContext.ProductionAppContext.class})
public class AppContext {
. . .
@Configuration
@Profile("production")
public static class ProductionAppContext {
. . .
}
@Configuration
@Profile("test")
public static class TestAppContext {
. . .
}
} 두 클래스에 static만 붙여서 AppContext로 가져온 뒤 @import에 지정했던 클래스를 내부로 이동시킨 클래스로 바꿔주기만 하면 된다. 이제 AppContext만 열어보면 디폴트로 적용될 빈이 무엇인지, 프로파일을 지정하기에 따라 어떤 빈이 등록될지, 또 프로파일에 따라 7.6.5 프로퍼티 소스AppContext에는 여전히 테스트 환경에 종속되는 정보가 남아있다. dataSource의 DB 연결 정보이다. 그래서 이런 정보들은 자바 코드에 직접 하드코딩 하는 대신, XML이나 프로퍼티 파일같은 텍스트 파일에 저장해 두는 것이 좋다. db.driverClass=com.mysql.jdbc.Driver
db.url=jdbc:mysql://....
db.username=spring
db.password=book 빈 설정에 필요한 프로퍼티를 외부 정보로 부터 가져올 수 있다. @Configuration
@EnableTransactionManagement
@ComponentScan(basePackages="springbook.user")
@Import(SqlServiceContext.class)
@PropertySource("/database.properties")
public class AppContext { 이렇게 아래처럼 등록해두면 컨테이너가 관리하는 Environment 타입의 환경 오브젝트에 프로퍼티가 저장된다. @Autowired Environment env;
@Bean
public DataSource dataSource {
SimpleDriverDataSource ds = new SimpleDriverDataSource();
try {
ds.setDriverClass((Class<?
extends java.sql.Driver>)Class.forName(env.getProperty("db.driverClass")));
}
catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
ds.setUrl(env.getProperty("db.url"));
ds.setUsername(env.getProperty("db.username"));
ds.setPassword(env.getProperty("db.password"));
return ds;
} PropertySourcesPlaceholderConfigurer에서 @value 애노테이션을 통해 치환자로 프로퍼티를 소스로부터 직접 주입받을 수 있다. @PropertySource("/database.properties")
public class AppContext {
@Value("${db.driverClass}") Class<? extends Driver> driverClass;
@Value("${db.url}") String url;
@Value("${db.username}") String username;
@Value("${db.password}") String password; @value와 치환자로 프로퍼티 값을 필드에 주입하려면 PropertySourcesPlaceholderConfigurer 빈을 정의해주어야 한다. @Bean
public static PropertySourcesPlaceholderConfigurer placeholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
} 7.6.6 빈 설정의 재사용과 @enable*SqlServiceContext는 조금 특별한 특징이 있어 AppContext와 분리하도록 했다. 그러나 여전히 SQL 서비스는 특정 위치의 특정 파일에 의존적인 문제가 있다. private class OxmSqlReader implements SqlReader {
private Unmarshaller unmarshaller;
private Resource sqlmap = new ClassPathResource("sqlmap.xml", UserDao.class);
} UserDao의 클래스 패스의 sqlmap.xml로 고정되어있다. SQL 서비스를 재사용 가능한 독립적인 모듈로 만들려면 UserDao 위치로 고정되어있는 SQL매핑파일의 위치를 직접 지정할 수 있도록 수정해주어야 한다. 아래는 위 인터페이스를 구현한 클래스이다. public class UserSqlMapConfig implements SqlMapConfig{
@Override
public Resource getSqlMapResource() {
return new ClassPathResource("/sqlmap.xml", UserDao.class);
}
} 그 후 SqlServiceContext가 변하지 않는 SqlMapConfig 라는 인터페이스에만 의존하게 만들고, 구현 클래스는 빈의로 정의해 런타임 시 주입되게 만든다. @Configuration
public class SqlServiceContext {
@Autowired SqlMapConfig sqlMapConfig;
@Bean
public SqlService sqlService() {
OxmSqlService sqlService = new OxmSqlService();
sqlService.setUnmarshaller(unmarshaller());
sqlService.setSqlRegistry(sqlRegistry());
sqlService.setSqlmap(new ClassPathResource("/sql/sql-map.xml", UserDao.class));
return sqlService;
} 그 후 SqlMapConfig을 구현한 UserSqlMapConfig 클래스를 빈으로 등록 해준다. public class AppContext {
. . .
@Bean
public SqlMapConfig sqlMapConfig() {
return new UserSqlMapConfig();
}
} @enable*애노테이션 @Import(value = SqlServiceContext.class)
public @interface EnableSqlService {
}
// 메타 애노테이션으로 넣은 애노테이션 정의 @Configuration
@EnableTransactionManagement
@ComponentScan(basePackages="springbook.user")
@EnableSqlService
@PropertySource("/database.properties")
public class AppContext implements SqlMapConfig{ 이렇게 하니 전보다 가독성이 더 좋아졌다. 7.7 정리
|
chapter 9. 스프링 프로젝트 시작하기
|
8장 정리스프링의 정의자바로의 개발을 편하게 해주는 오픈소스 경량급 ** 애플리케이션 프레임워크 ** 애플리케이션 프레임워크 스프링의 기원스프링은 자바 엔터프라이즈 개발 전략의 핵심 을 그대로 이어 개발 되었다. 편리한 애플리케이션 개발
=> 스프링은 애플리케이션 비지니스 로직에 집중하도록 도와줌. 스프링의 목적
DI를 사용해서, 비즈니스 로직은 최대한 순수하게 가져가자. POJO 프로그래밍POJO의 조건
스프링의 기술POJO 프로그래밍을 위해 spring이 지원하는 세가지 가능기술: IoC/DI, AOP, PSA IoC/DIDI를 하는 이유 사용할 대상은 DI를 통해 외부에서 지정한다. 핵심 기능의 변경 실제 의존하는 대상이 가진 핵심 기능을 DI설정을 통해 변경 인터페이스의 변경 오브젝트가 가진 인터페이스가 클라이언트와 호환되지 않는 경우, 어댑터 패턴 적용자주 바뀌는 부분을 정리해서 템플릿, 콜백으로 만들자. 싱글톤과 오브젝트 스코프 테스트의존성이 있다면, mock object 주입. AOP보통 IoC/DI 을 통해 POJO 프로그래밍을 하지만, AOP의 적용 기법 AspectJ 사용 - 메서드 호출, 필드 액세스, 인스턴스 생성 등에도 부가 기능 제공 가능 AOP의 적용트랜잭션(@transactional) 보안, 로깅, 데이터 추적, 성능 모니터링 같은 기능에 적용하면 유용하다. PSA구체적 기술, 설정은 xml파일로 지정하자. |
The text was updated successfully, but these errors were encountered: