토비의 스프링 정리
토비의 스프링 - 7.2 인터페이스의 분리와 자기참조 빈
ksb-dev
2022. 10. 25. 22:56
7.2.1 JAXB(Java Architecture for XML Binding)
- XML에 담긴 정보를 파일에 읽어오는 방법 중 하나
- JAXB는 DOM과 과 같은 전통적인 XML API와 비교했을 때, XML 문서정보를 거의 동일한 구조의 오브젝트로 직접 매칭해줌
- DOM은 XML 정보를 마치 자바의 리플렉션 API를 사용해 조작 하는것 처럼 간접적으로 접근해야 함
- JAXB는 XML의 정보를 그대로 담고 있는오브젝트 트리를 만들어주기 때문에, XML 정보를 오브젝트 처럼 직접적으로 다룰 수 있어 편리함
- JAXB는 XML 문서의 구조를 정의한 스키마를 이용해서 매핑할 오브젝트의 클래스를 자동으로 만들어주는 컴파일러도 제공함
- 스키마 컴파일러를 통해 자동생성 된 오브젝트에는 매핑정보가 애노테이션으로 담겨 있음
- JAXB API는 애노테이션에 담긴 정보를 이용해 XML과 매핑괸 오브젝트 트리 사이의 자동변환 작업을 수행함
7.2.2 SQL 맵을 위한 스키마 작성과 컴파일
- <sqlmap>, <sql> 구조를 정의하는 스키마 생성
- 스키마 이름은 sqlmap.xsd
// <https://mvnrepository.com/artifact/javax.xml.bind/jaxb-api>
implementation group: 'javax.xml.bind', name: 'jaxb-api', version: '2.3.1'
<?xml version="1.0" encoding="UTF-8" ?>
<schema xmlns="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.epril.com/sqlmap"
xmlns:tns="http://www.epril.com/sqlmap" elementFormDefault="qualified">
<element name="sqlmap">
<complexType>
<sequence>
<!--unbounded : 필요한 개수만큼 <sql>을 포함할 수 있게 함-->
<element name="sql" maxOccurs="unbounded" type="tns:sqlType"/>
</sequence>
</complexType>
</element>
<!--<sql>에 대한 정의 시작-->
<complexType name="sqlType">
<simpleContent>
<!--SQL 문장을 넣을 스트링 타입 지정-->
<extension base="string">
<!--검색을 위한 키 값은 <sql>의 key 애트리뷰트에 넣음. 필수값임-->
<attribute name="key" use="required" type="string"/>
</extension>
</simpleContent>
</complexType>
</schema>
- 셸 또는 도스 창에서 프로젝트 루트 폴더로 이동한 뒤 다음 명령어 사용해 컴파일
xjc -p {패키지이름} {변환할 스키마 파일} -d {생성된 파일이 저장될 위치}
xjc -p com.ksb.spring.jaxb sqlmap.xsd -d src
- 💡 현재 명령어 실행 불가!!
7.2.3 책 내용의 문제점 및 대안방법
- 토비의 스프링 책이 발간된지 오래됐음 때문에 레거시 문제가 있음
- 또한, 과거에는 지원 했으나 현재는 지원하지 않는 기능들이 존재함
- JAXB가 대표적인 예시임
- 기존 JDK에 존재하는 JABX 스키마 컴파일러가 JDK 11부터 사라졌음
- 때문에, 책에서 나온 명령어를 통한 JABX 컴파일러 실행은 따로 JAXB 스키마 컴파일러를 받아 실행시키지 않는 이상 불가능함
- 💡 별도로 JAXB 스키마 컴파일러를 받아서 시도 해 봤는데 실패했음
- 대안방법은 존재함(IntellJ Ultimate 사용자만 가능!)
- 변환하고자 하는 xsd 파일로 이동
- IntelliJ에서 Ctrl+ALT+A 를 동시에 누르기
- JAXB 검색
- Generate Java Code From XML Schema Using XMLBeans 선택
- 원하는 항목 채워넣고 엔터 누르면 파일이 생김
- sqlmap.xsd에서 실행시 Sqlmap, SqlType 클래스 생성
//변환 작업에서 참고할 정보를 애노테이션으로 갖고있음
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
"sql"
})
@XmlRootElement(name = "sqlmap", namespace = "<http://www.epril.com/sqlmap>")
public class Sqlmap {
@XmlElement(namespace = "<http://www.epril.com/sqlmap>", required = true)
protected List sql
public List getSql() {
if (sql == null) {
sql = new ArrayList();
}
return this.sql;
}
}
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "sqlType", namespace = "<http://www.epril.com/sqlmap>", propOrder = {
"value"
})
public class SqlType { // 태그 한 개당 SqlType 오브젝트 하나 씩 생성
@XmlValue
protected String value; //SQL 값을 저장할 스트링 타입 필드
@XmlAttribute(name = "key", required = true)
protected String key; //검색용 키 값
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public String getKey() {
return key;
}
public void setKey(String value) {
this.key = value;
}
}
7.2.4 언마샬링 및 학습 테스트
- XML 문서를 읽어서 자바의 오브젝트로 변환하는 작업을 JAXB에서 언마샬링(Unmarshalling)이라 함
- 이와 대조적으로, 바인딩 오브젝트를 XML 문서로 변환하는 것을 마샬링(Marshalling)이라 함
- JABX API를 이용해 언마샬링이 되는지 테스트 할 것임
- 테스트용 SQL 맵 XML 문서 이름 sqlmap.xml
<?xml version="1.0" encoding="UTF-8" ?>
<sqlmap xmlns="http://www.epril.com/sqlmap"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.epril.com/sqlmap ./sqlmap.xsd">
<sql key="add">insert</sql>
<sql key="get">select</sql>
<sql key="delete">delete</sql>
</sqlmap>
import com.ksb.spring.jaxb.SqlType;
import com.ksb.spring.jaxb.Sqlmap;
...
public class JaxbTest {
@Test
public void readSqlmap() throws JAXBException, IOException {
//바인딩용 클래스들 위치를 가지고 JAXB 컨텍스트를 만듦
String contextPath = Sqlmap.class.getPackage().getName();
JAXBContext context = JAXBContext.newInstance(contextPath);
//언마샬러 생성. xml -> 자바 오브젝트
Unmarshaller unmarshaller = context.createUnmarshaller();
//언마샬을 하면 매핑된 오브젝트 트리의 루트인 Sqlmap을 반환
Sqlmap sqlmap = (Sqlmap) unmarshaller.unmarshal(
getClass().getResourceAsStream("sqlmap.xml")
);
//List에 담겨 있는 Sql 오브젝트를 가져와 XML 문서와 같은 정보를 갖는지 확인
List<SqlType> sqlList = sqlmap.getSql();
assertThat(sqlList.size() ,is(3));
assertThat(sqlList.get(0).getKey(), is("add"));
assertThat(sqlList.get(0).getValue(), is("insert"));
assertThat(sqlList.get(1).getKey(), is("get"));
assertThat(sqlList.get(1).getValue(), is("select"));
assertThat(sqlList.get(2).getKey(), is("delete"));
assertThat(sqlList.get(2).getValue(), is("delete"));
}
}
7.2.5 SQL 맵 XML 파일
- UserDaoJdbc에 사용할 SQL이 담긴 XML 문서 생성 할 것임
- 스프링 설정의 <map>, <entry>에 담아뒀을 때 보다 의미가 명확함
- SQL은 DAO의 로직 일부이기 때문에 같은 패키지에 두는것이 좋음
- 위에서 만든 sqlmap.xml을 수정
<?xml version="1.0" encoding="UTF-8" ?>
<sqlmap xmlns="http://www.epril.com/sqlmap"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.epril.com/sqlmap ./sqlmap.xsd">
<sql key="userAdd">insert into users(id, name, password, level, login, recommend) value (?,?,?,?,?,?)</sql>
<sql key="userGet">select * from users where id = ?</sql>
<sql key="userGetAll">select * from users order by id</sql>
<sql key="userDeleteAll">delete from users</sql>
<sql key="userGetCount">select count(*) from users</sql>
<sql key="userUpdate">update users set name=?, password=?, level=?, login=?, recommend=? where id=?</sql>
</sqlmap>
7.2.6 XML SQL 서비스
- sqlmap.xml에 있는 SQL을 가져와 DAO에 제공하는 SqlService 인터페이스의 구현 클래스를 만들 것임
- 언제 JAXB를 이용해 XML 문서를 가져올지 생각해봐야 함
- DAO가 SQL 요청 할 때마다 매번 XML 파일을 읽는 것은 비효율적임
- 특별한 이유가 없는 한, XML 파일은 한 번만 읽도록 해야 함
- XML 파일로 부터 읽은 내용은 어딘가에 저장해두고 DAO에 요청이 올 때 사용되야 함
- 처음 SQL을읽는 건 어디서 해야 하는지에 대해서도 고민해야 함
- SqlService를 구현한 클래스는 빈에 등록이 될 것이고, 언제 어떻게 빈 오브젝트를 생성할 지 알 수 없으니 일단 생성자에서 SQL을 읽어와 내부에 저장하는 초기 작업을 할 것임
- 이후에 리팩토링 하면서 코드를 개선할 것음
- JAXB로 XML 문서를 언마샬링하면 SQL 문장 하나는 Sql 클래스 오브젝트에 하나씩 담길 것임
- Map 타입 오브젝트에 저장 하는것이 좋음
- UserDaoTest를 통해 테스트 성공
public class XmlSqlService implements SqlService{
private Map<String, String> sqlMap = new HashMap<>();
public XmlSqlService(){
String contextPath = Sqlmap.class.getPackage().getName();
try{
//JAXB API를 이용해 XML 문서를 오브젝트 트리로 읽어옴
JAXBContext context = JAXBContext.newInstance(contextPath);
Unmarshaller unmarshaller = context.createUnmarshaller();
//SqlType와 같은 클래스 패스의 sqlmap.xml 파일 변환
/*
프로젝트 위치에 슬래시(/) 꼭 넣어 줘야 함.
이거 때문에 5일간 개고생;;
*/
InputStream is = getClass().getResourceAsStream("/sqlmap.xml");
Sqlmap sqlmap = (Sqlmap) unmarshaller.unmarshal(is);
//읽어온 SQL을 맵으로 저장
for(SqlType sql : sqlmap.getSql())
sqlMap.put(sql.getKey(), sql.getValue());
} catch (JAXBException e){
throw new RuntimeException(e);
}
}
@Override
public String getSql(String key) throws SqlRetrievalFailException {
String sql = sqlMap.get(key);
if(sql == null)
throw new SqlRetrievalFailException(key+"에 대한 SQL을 찾을 수 없습니다.");
else
return sql;
}
}
<beans
...>
<bean id="sqlService" class="com.ksb.spring.XmlSqlService">
</bean>
</beans>
7.2.7 빈의 초기화 작업
- 생성자에서 예외가 발생할 수 있는 복잡한 초기화 작업을 다루는 것은 좋지 않음
- 또한, 읽어들일 파일의 위치와 이름이 코드에 고정되는 것은 좋지 않음
- 파일 위치를 DI 받게 수정
public class XmlSqlService implements SqlService{
...
private String sampleFile;
public void setSampleFile(String sampleFile){
this.sampleFile = sampleFile;
}
public void XmlSqlService(){
...
try{
...
InputStream is = UserDao.class.getResourceAsStream(sampleFile);
...
} catch (JAXBException e){
throw new RuntimeException(e);
}
}
...
}
<bean id="sqlService" class="com.ksb.spring.XmlSqlService">
<property name="sampleFile" value="/sqlmap.xml"/>
</bean>
- 생성자가 아닌 일반 메소드에서 복잡한 초기화 작업을 할 수 있도록 하는 스프링의 빈 후처리기가 존재함
- 특정 애노테이션들을 지원하는 빈 후처리기를 사용할 것임
- 해당 빈 후처리기를 사용하기 위해서는 xml에 <context:annotation-config/>를 추가해야 함
<beans
...
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="...
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<!--@Transactional이 붙은 타입과 메소드에 트랜잭션 추가 후처리기 등록-->
<tx:annotation-driven/>
<!--특정 애노테이션을 사용해 빈 설정 또는 초기화 작업을 하는 빈 후처리기 등록-->
<context:annotation-config/>
</beans>
- 생성자를 일반 메소드인 loadSql()로 변경한 뒤에, @PostConstruct 추가
public class XmlSqlService implements SqlService{
...
@PostConstruct
public void loadSql(){
...
}
}
- @PostConstruct가 붙은 메소드는 빈 오브젝트가 생성되고 DI 작업이 다 끝난 뒤에 호출됨
7.2.8 책임에 따른 인터페이스 정의
- 현재 XmlSqlService는 특정 포맷의 XML에서 데이터를 가져오고, 이를 HashMap 타입의 맵 오브젝트에 저장함
- JAXB를 이용한 XML이 아닌, 다른 포맷에서 데이터를 가져오려면 코드를 완전히 수정해야 함
- 관심사의 분리를 통해 유연하게 확장하도록 할 것임
- 두 가지 책임으로 분리할 수 있음
- SQL 정보를 외부의 리소스로부터 데이터 전체를 읽어오는 것 - SqlReader
- 읽어온 SQL을 보관(HashMap)해두고 있다가 필요할 때 제공 - SqlRegister의 getSql()
- 💡 사용중인 SQL을 필요에 따라 수정할 수 있는 부가적 책임도 생각할 수 있음
- 두 가지 책임을 분리해서 동작하도록 전략 패턴을 이용할 수 있음
- DAO 관점에서는 SqlService라는 인터페이스를 구현한 오브젝트에만 의존하는 것이므로 달라질게 없음
- 하지만, SqlService의 구현 클래스가 변경 가능한 책임을 가진 SqlReader와 SqlRegister 두 가지 타입의 오브젝트를 사용하도록 함
- SqlReader와 SqlRegister은 전략 패턴으로 DI를 통해 의존 오브젝트를 제공받도록 함
- 추가적으로, SqlRegister의 일부 인터페이스는 SqlService가 아닌 다른 오브젝트(SqlUpdater)가 사용할 수 있음
- 💡 대표적으로, SQL을 필요에 따라 SQL을 수정하여 SQL의 데이터를 바꿔 애플리케이션을 재시작하지 않고 SQL을 긴급히 변경할 수 있게 함
- SqlReader가 읽어오고, 해당 읽어온 데이터를 SqlRegister에 저장해서 사용하는 구조임
- 정확히 SqlReader에서 읽어온 데이터를 Map 타입의 형식으로 리턴해서, SqlRegister로 전달하는 구조임
- 만약 SqlRegister가 Map 타입이 아닌 2차원 배열 구조를 사용하면, Map을 2차원 배열 구조로 변환하는 작업이 필요함
- 하지만, SqlService의 구현 클래스가 꼭 SqlReader를 읽어 데이터를 SqlRegister로 전달할 필요가 없음
- SqlReader에게 SqlRegister 전략을 제공하여 SqlRegister에 SQL 정보를 저장하라고 요청하는 구조가 더욱 좋음
7.2.9 SqlRegister 인터페이스
- SQL을 등록하고 검색하는 두 가지 기능을 메소드로 정의
- 검색 도중 예외 발생의 경우, 복구 가능성이 낮기 때문에 런타임 예외로 포장
- 여러개의 레지스트리가 있을 경우 한 레지스트리에서 검색 도중 예외 발생 시, 다른 레지스트리에 검색 시도를 하면 복구 가능성이 높아 짐
public interface SqlRegistry {
void registrySql(String key, String sql); //sql을 검색할 수 있도록 key와 함께 등록
String findSql(String key) throws SqlRetrievalFailException;
}
7.2.10 SqlReader 인터페이스
- SqlRegistry 오브젝트르 메소드 파라미터로 DI 받아서 읽어들인 SQL을 등록하도록 해야 함
public interface SqlReader {
//예외 발생 시 대부분 복구가 불가능 하므로 굳이 예외 선언X
void read(SqlRegistry sqlRegistry);
}
7.2.11 다중 인터페이스 구현과 간접 참조
- SqlService의 구현 클래스는 SqlReader와 SqlRegister 두 개의 프로퍼티를 DI 받아야 함
- 한 클래스에서 여러 개의 인터페이스를 구현할 수 있음
- XmlSqlService는 인터페이스만 구현하기 때문에 세 개의 인퍼페이스를 한 번에 구현할 수 있음
- 구현한 인터페이스 서로 책임이 다름
- XmlSqlService에서 책임이 다른 코드를 직접 접근하지 않고, 인터페이스를 통해 간접적으로 사용하는 코드로 분리할 것임
7.2.12 인터페이스를 통한 분리
- XmlSqlService는 SqlReader와 SqlRegister 각각 구현한 오브젝트를 DI 받아야 함
- XmlSqlService 코드 구현 순서
- 1. 오브젝트 DI
public class XmlSqlService implements SqlService{
private SqlReader sqlReader;
private SqlRegistry sqlRegistry;
public void setSqlReader(SqlReader sqlReader) {
this.sqlReader = sqlReader;
}
public void setSqlRegistry(SqlRegistry sqlRegistry) {
this.sqlRegistry = sqlRegistry;
}
public void setSqlmapFile(String sqlmapFile) {
this.sqlmapFile = sqlmapFile;
}
}
- 2. SqlRegistry
public class XmlSqlRegistry implements SqlRegistry {
//sqlMap은 SqlRegistry 구현의 일부가 되었으므로,
// 책임이 다른 곳에서 직접 접근하면 안됨
private Map<String, String> sqlMap = new HashMap<>();
...
@Override
public void registrySql(String key, String sql) {
sqlMap.put(key, sql);
}
@Override
public String findSql(String key) throws SqlRetrievalFailException {
String sql = sqlMap.get(key);
if (sql == null)
throw new SqlRetrievalFailException(key + "에 대한 SQL을 찾을 수 없습니다.");
else
return sql;
}
}
- 3. SqlReader
public class XmlSqlReader implements SqlRegistry, SqlReader {
private String sqlmapFile;
public void setSqlmapFile(String sqlmapFile) {
this.sqlmapFile = sqlmapFile;
}
...
@Override
public void read(SqlRegistry sqlRegistry) {
String contextPath = Sqlmap.class.getPackage().getName();
try {
JAXBContext context = JAXBContext.newInstance(contextPath);
Unmarshaller unmarshaller = context.createUnmarshaller();
InputStream is = UserDao.class.getResourceAsStream(sqlmapFile);
Sqlmap sqlmap = (Sqlmap) unmarshaller.unmarshal(is);
for (SqlType sql : sqlmap.getSql()) {
//sqlMap 직접 사용하지 않고, 간접적 사용
sqlRegistry.registrySql(sql.getKey(), sql.getValue());
}
} catch (JAXBException e) {
throw new RuntimeException(e);
}
}
}
- 4. SqlService
public class XmlSqlService implements SqlService, SqlRegistry, SqlReader {
...
@PostConstruct
public void loadSql() {
this.sqlReader.read(this.sqlRegistry);
}
@Override
public String getSql(String key) throws SqlRetrievalFailException {
try{
return this.sqlRegistry.findSql(key);
}catch (SqlRetrievalFailException e){
throw new RuntimeException(e);
}
}
}
7.2.13 자기참조 빈 설정
- XmlSqlService 클래스 내부에 세 가지 인터페이스 구현이 있지만, 간접적 사용을 통해 서로 깔끔하게 분리 되었음
- 같은 코드에 있지만, 빈 설정을 통해 실제 DI가 발생하도록 해야 함
- 즉, 마치 세 개의 빈이 등록된 것처럼 SqlService 빈이 SqlRegistry와 SqlReader를 주입받도록 해야 함
- 빈은 sqlService 하나만 선언 했으므로 실제 빈은 하나만 만들어짐
- 하지만, ref 항목에 자기 자신을 넣어 자기 자신 메소드에 접근할 수 있게 함
- 이 빈을 자기참조 빈이라 함
- 잘 사용되지는 않지만, 책임과 관심사가 복잡하게 얽힌 구조를 유연한 구조로 만들 때 처음 시도해 볼 수 있음
<bean id="sqlService" class="com.ksb.spring.XmlSqlService">
<property name="sqlmapFile" value="/sqlmap.xml"/>
<!--자기참조 빈-->
<property name="sqlReader" ref="sqlService"/>
<property name="sqlRegistry" ref="sqlService"/>
</bean>
7.2.14 확장 가능한 기반 클래스
- SqlRegistry와 SqlReader를 이용한 가장 간단한 SqlService 구현 클래스를 만들 것임
- XmlSqlService 처럼 세 개의 인터페이스를 하나의 클래스에서 구현하지 않고, 각각의 클래스로 분리
- SqlService 구현 클래스 이름은 BaseSqlService
public class BaseSqlService implements SqlService{
//상속을 통한 확장을 위해 protected로 선언
protected SqlReader sqlReader;
protected SqlRegistry sqlRegistry;
public void setSqlReader(SqlReader sqlReader) {
this.sqlReader = sqlReader;
}
public void setSqlRegistry(SqlRegistry sqlRegistry) {
this.sqlRegistry = sqlRegistry;
}
@PostConstruct
public void loadSql() {
this.sqlReader.read(this.sqlRegistry);
}
@Override
public String getSql(String key) throws SqlRetrievalFailException {
try{
return this.sqlRegistry.findSql(key);
}catch (SqlRetrievalFailException e){
throw new RuntimeException(e);
}
}
}
- SqlRegister 구현 클래스
public class HashMapSqlRegistry implements SqlRegistry{
private Map<String, String> sqlMap = new HashMap<>();
@Override
public void registrySql(String key, String sql) {
sqlMap.put(key, sql);
}
@Override
public String findSql(String key) throws SqlRetrievalFailException {
String sql = sqlMap.get(key);
if (sql == null)
throw new SqlRetrievalFailException(key + "에 대한 SQL을 찾을 수 없습니다.");
else
return sql;
}
}
- SqlReader 구현 클래스
public class JaxbXmlSqlReader implements SqlReader{
private String sqlmapFile;
public void setSqlmapFile(String sqlmapFile) {
this.sqlmapFile = sqlmapFile;
}
@Override
public void read(SqlRegistry sqlRegistry) {
String contextPath = Sqlmap.class.getPackage().getName();
try {
JAXBContext context = JAXBContext.newInstance(contextPath);
Unmarshaller unmarshaller = context.createUnmarshaller();
InputStream is = UserDao.class.getResourceAsStream(sqlmapFile);
Sqlmap sqlmap = (Sqlmap) unmarshaller.unmarshal(is);
for (SqlType sql : sqlmap.getSql()) {
//sqlMap 직접하용하지 않고, 간접적 사용
sqlRegistry.registrySql(sql.getKey(), sql.getValue());
}
} catch (JAXBException e) {
throw new RuntimeException(e);
}
}
}
- 빈 설정
<beans>
<bean id="sqlService" class="com.ksb.spring.BaseSqlService">
<property name="sqlReader" ref="sqlReader"/>
<property name="sqlRegistry" ref="sqlRegistry"/>
</bean>
<bean id="sqlReader" class="com.ksb.spring.JaxbXmlSqlReader">
<property name="sqlmapFile" value="/sqlmap.xml"/>
</bean>
<bean id="sqlRegistry" class="com.ksb.spring.HashMapSqlRegistry">
</bean>
</beans>
7.2.15 디폴트 의존관계를 갖는 빈 만들기
- BaseSqlService는 SqlReader와 SqlRegistry 프로퍼티의 DI를 받아 기능을 확장할 수 있음
- 하지만, 세 개의 빈을 등록해야 하는 불편함 존재
- DI 받는 빈의 클래스가 변경될 가능성이 적어 디폴트라고 해도 좋을 만큼 기본적으로 사용될 가능성이 있다면, 디폴트 의존관계를 갖는 빈을 만드는 것을 고려할 수 있음
- 디폴트 의존관계란, 외부에서 DI 받지 않는 경우 자동 적용되는 의존 관계를 뜻함
public class DefaultSqlService extends BaseSqlService{
public DefaultSqlService(){
//생성자에서 디폴트 의존 관계를 수동 DI
setSqlReader(new JaxbXmlSqlReader());
setSqlRegistry(new HashMapSqlRegistry());
}
}
<bean id="sqlService" class="com.ksb.spring.DefaultSqlService"/>
- 위 코드의 테스트는 실패함
- 이유는, sqlmapFile을 읽는 파일 이름을 넣을 수 없기 때문임
- 이를 해결하기 위해, 기본적으로 디폴트 파일 이름을 코드에서 할당하면 됨
public class JaxbXmlSqlReader implements SqlReader{
private static final String DEFAULT_SQLMAP_FILE = "/sqlmap.xml";
private String sqlmapFile = DEFAULT_SQLMAP_FILE;
public void setSqlmapFile(String sqlmapFile) {
this.sqlmapFile = sqlmapFile;
}
...
}
- DefaultSqlService는 SqlService를 바로 구현하지 않고, BaseSqlService을 상속한 이유가 있음
- BaseSqlService은 SqlReader와 SqlRegistry 프로퍼티를 갖고 있음
- 만약 디폴트 대신에 사용할 빈이 있을 경우 빈에서 프로퍼티를 넣으면 됨
<bean id="sqlService" class="com.ksb.spring.DefaultSqlService">
<property name="sqlRegistry" ref="anotherSqlRegistry" />
<bean>
7.2.16 디폴트 의존 오브젝트 사용
- 디폴트 의존 오브젝트는 설정을 통해 다른 구현 오브젝트를 사용해도 생성자에서 일단 디폴트 의존 오브젝트를 전부 만든다는 단점이 있음
- 사용되지 않는 오브젝트가 생성될 수 있음
- 하지만, 디폴트 의존 오브젝트의 장점이 크기 때문에 오브젝트 하나쯤 더 만들어지는 부담은 무시할 수 있음