토비의 스프링 정리

토비의 스프링 - 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 사용자만 가능!)
    1. 변환하고자 하는 xsd 파일로 이동
    2. IntelliJ에서 Ctrl+ALT+A 를 동시에 누르기
    3. JAXB 검색
    4. Generate Java Code From XML Schema Using XMLBeans 선택
    5. 원하는 항목 채워넣고 엔터 누르면 파일이 생김
  • 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이 아닌, 다른 포맷에서 데이터를 가져오려면 코드를 완전히 수정해야 함
  • 관심사의 분리를 통해 유연하게 확장하도록 할 것임
  • 두 가지 책임으로 분리할 수 있음
    1. SQL 정보를 외부의 리소스로부터 데이터 전체를 읽어오는 것 - SqlReader
    2. 읽어온 SQL을 보관(HashMap)해두고 있다가 필요할 때 제공 - SqlRegister의 getSql()
    3. 💡 사용중인 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 디폴트 의존 오브젝트 사용

  • 디폴트 의존 오브젝트는 설정을 통해 다른 구현 오브젝트를 사용해도 생성자에서 일단 디폴트 의존 오브젝트를 전부 만든다는 단점이 있음
  • 사용되지 않는 오브젝트가 생성될 수 있음
  • 하지만, 디폴트 의존 오브젝트의 장점이 크기 때문에 오브젝트 하나쯤 더 만들어지는 부담은 무시할 수 있음