토비의 스프링 Vol.1 - 7장 스프링 핵심 기술의 응용

2020-11-03

스프링 핵심 기술의 응용

SQL과 DAO의 분리

SQL 변경이 필요한 상황이 발생하면 SQL을 담고 있는 DAO 코드가 수정될 수밖에 없다.

XML 설정을 이용한 분리

스프링은 설정을 이용해 빈에 값을 주입해줄 수 있다. SQL은 문자열로 되어 있으니 설정파일에 프로퍼티 값으로 정의해서 DAO에 주입해줄 수 있다. 이렇게 하면 설정파일에 있는 SQL을 코드와는 독립적으로 수정할 수가 있다.

개별 SQL 프로퍼티 방식
  • add() 메소드를 위한 SQL 필드
    public class UserDaoJdbc implements UserDao {
    private String sqlAdd;
      
    public void setSqlAdd(String sqlAdd) {
      this.sqlAdd = sqlAdd;
    }
    }
    
  • 주입받은 SQL 사용
    public void add(User user) {
    this.jdbcTemplate.update(
      this.sqlAdd, // "insert into users..."를 제거하고 외부에서 주입받은 SQL을 사용하게 한다.
      user.getId(), user.getNamq(), user.getPassword(), user.getEmail(),
      user.getLevel().intValue(), user.getLogin(), user.getRecommend());
    )
    }
    
  • 설정파일에 넣은 SQL 문장 ```
<property name="dataSource ref="dataSource" /> ... ``` > 스프링에서는 스트링 값을 외부에서 DI 해서 사용할 수 있기 때문에 손쉽게 SQL을 분리하는 데 성공했다. 하지만 이 방법은 매번 새로운 SQL이 필요할 때마다 프로퍼티를 추가하고 DI를 위한 변수와 수정자 메소드도 만들어줘야 하기 때문에 불편하다. ##### SQL 맵 프로퍼티 방식 SQL을 하나의 컬렉션으로 담아두는 방법 * 맵 타입의 SQL 정보 프로퍼티 ``` public class UserDaoJdbc implements UserDao { ... private Map<String, String> sqlMap; public void setSqlMap(Map<String, String> sqlMap) { this.sqlMap = sqlMap; } } ``` * sqlMap을 사용하도록 수정한 add() ``` public void add(User user) { this.jdbcTemplate.update( this.sqlMap.get("add"), // 프로퍼티로 제공받은 맵으로부터 키를 이용해서 필요한 SQL을 가져온다. user.getId(), user.getNamq(), user.getPassword(), user.getEmail(), user.getLevel().intValue(), user.getLogin(), user.getRecommend()); ) } ``` * 맵을 이용한 SQL 설정 ``` <property name="dataSource ref="dataSource" /> ``` > 맵으로 만들어두면 새로운 SQL이 필요할 때 설정에 entry만 추가해주면 되니 모든 SQL을 일일이 프로퍼티로 등록하는 방법에 비해 작업량도 적고 코드도 간단해서 좋다. 대신 메소드에서 SQL을 가져올 때 문자열로 된 키 값을 사용하기 때문에 오타와 같은 실수가 있어도, 해당 메소드가 실행되기 전에는 오류를 확인하기 힘들다는 단점이 있다. #### SQL 제공 서비스 SQL을 꼭 스프링의 빈 설정 방법을 사용해 XML에 담아둘 이유도 없다. SQL을 편집하고 관리할 수 있는 툴에서 생성해주는 SQL 정보 파일이 있다면, 그런 파일 포맷 그대로 사용할 수 있어야 편할 것이다. SQL 제공 기능을 본격적으로 분리해서 다양한 SQL 정보 소스를 사용할 수 있고, 운영 중에 동적으로 갠신도 가능한 유연하고 확장성이 뛰어난 SQL 서비스를 만들어야 한다. ##### SQL 서비스 인터페이스 클라이언트인 DAO를 SQL 서비스의 구현에서 독립적으로 만들도록 인터페이스를 사용하고, DI로 구현 클래스의 오브젝트를 주입해주어야 한다. * SqlService 인터페이스 ``` public interface SqlService { String getSql(String key) throws SqlRetrievalFailureException; // 런타임 예외이므로 특별히 복구해야 할 필요가 없다면 무시해도 된다. } ``` * 조회 실패 시 예외 ``` public class SqlRetrievalFailureException extends RuntimeException { public SqlRetrievalFailureException(String message) { super(message); } // SQL을 가져오는 데 실패한 근본 원인을 담을 수 있도록 중첩 예외를 저장할 수 있는 생성자를 만들어둔다. public SqlRetrievalFailureException(String message, Throwable cause) { super(message, cause); } } ``` * 맵을 이용한 SqlService의 구현 ``` public class SimpleSqlService implements SqlService { // 설정파일에 으로 정의된 SQL정보를 가져오도록 프로퍼티로 등록해둔다. private Map<String, String> sqlMap; public void setSqlMap(Map<String, String> sqlMap) { this.sqlMap = sqlMap; } public String getSql(String key) throws SqlRetrievalFailureException { String sql = sqlMap.get(key); // 내부 SqlMap에서 SQL을 가져온다. if (sql == null) // 인터페이스에 정의된 규약대로 SQL을 가져오는 데 실패하면 예외를 던지게 한다. throw new SqlRetrievalFailureException(key + "에 대한 SQL을 찾을 수 없습니다"); else return sql; } } ``` > UserDao를 포함한 모든 DAO는 SQL을 어디에 저장해두고 가져오는지에 대해서는 전혀 신경 쓰지 않아도 된다. 구체적인 구현 방법과 기술에 상관없이 SqlService 인터페이스 타입의 빈을 DI 받아서 필요한 SQL을 가져다 쓰기만 하면 된다. > 동시에 sqlService 빈에는 DAO에는 전혀 영향을 주지 않은 채로 다양한 방법으로 구현된 SqlService 타입 클래스를 적용할 수 있다.
### 인터페이스의 분리와 자기참조 빈 #### XML 파일 매핑 스프링의 XML 설정파일에서 태그 안에 SQL 정보를 넣어놓고 활용하는 건 좋은 방법이 아니다. 그보다는 SQL을 저장해두는 전용 포맷을 가진 독립적인 파일을 이용하는 편이 바람직하다. 가장 편리한 포맷은 역시 XML이다. #### 빈의 초기화 작업 ``` ``` 위 태그에 의해 등록되는 빈 후처리기는 몇 가지 특별한 빈 설정에 사용되는 애노테이션을 제공한다. @PostConstruct는 java.lang.annotation 패키지에 포함된 공통 애노테이션의 한 가지로 JavaEE 5나 JDK 6에 포함된 표준 애노테이션이다. 스프링은 @PostConstruct 애노테이션을 빈 오브젝트의 초기화 메소드를 지정하는 데 사용한다. ``` @PostConstruct // loadSql() 메소드를 빈의 초기화 메소드로 지정한다. public void loadSql() {...} ``` 위와 같이 사용하면 스프링은 클래스로 등록된 빈의 오브젝트를 생성하고 DI 작업을 마친 뒤에 @PostConstruct가 붙은 메소드를 자동으로 실행해준다. 생성자와는 달리 프로퍼티까지 모두 준비된 후에 실행된다는 면에서 @PostConstruct 초기화 메소드는 매우 유용하다. * 스프링 컨테이너인 애플리케이션 컨텍스트가 XML 설정파일을 읽고 진행하는 작업의 순서 1. XML 빈 설정을 읽는다. ```applicationContext.xml``` 2. 빈의 오브젝트를 생성한다. `````` 3. 프로퍼티에 의존 오브젝트 또는 값을 주입한다. `````` `````` 4. 빈이나 태그로 등록된 후처리기를 동작시킨다. 코드에 달린 애노테이션에 대한 부가 작업 진행 ```@PostConstruct public void init() { ... }``` > @PostConstruct 애노테이션은 빈 오브젝트가 생성되고 의존 오브젝트와 설정 값을 넣어주는 DI 작업까지 마친 후에 호출된다. 따라서 @PostConstruct를 단 메소드의 코드는 모든 프로퍼티의 값이 준비됐다고 가정하고 적성하면 된다. #### 변화를 위한 준비: 인터페이스 분리 ##### 책임에 따른 인터페이스 정의 1. SQL 정보를 외부의 리소스로부터 읽어온다. 2. 읽어온 SQL을 보관해두고 있다가 필요할 때 제공해준다.(한 번 가져온 SQL을 필요에 따라 수정할 수도 있어야 한다.) > 기본적으로 SqlService를 구현해서 DAO에 서비스를 제공해주는 오브젝트가 이 두 가지 책임을 가진 오브젝트와 협력해서 동작하도록 만들어야 한다. SqlService의 구현 클래스가 변경 가능한 책임을 가진 SqlReader와 SqlRegistry 두 가지 타입의 오브젝트를 사용하도록 만든다. 당연히 인터페이스를 이용하게 하고, DI를 통해 의존 오브젝트를 제공받게 해야 한다. * SqlRegistry 인터페이스 ``` public interface SqlRegistry { void registerSql(String key, String sql); // SQL을 키와 함께 등록한다. String findSql(String key) throws SqlNotFoundException; // 키로 SQL을 검색한다. 검색이 실패하면 예외를 던진다. } ``` * SqlReader 인터페이스 ``` public interface SqlReader { void read(SqlRegistry sqlRegistry); // SQL을 외부에서 가져와 SqlRegistry에 등록한다. 다양한 예외가 발생할 수 있겠지만 대부분 복구 불가능한 예외이므로 굳이 예외를 선언해두지 않았다. } ``` #### 자기참조 빈으로 시작하기 * 자신을 참조하는 sqlService 빈 설정 ``` ``` > 빈은 sqlService 하나만 선언했기에 실제 빈 오브젝트도 한 개만 만들어진다. 스프링은 프로퍼티의 ref 항목에 자기 자신을 넣는 것을 허용한다. 이를 통해, sqlService를 구현한 메소드와 초기화 메소드는 외부에서 DI 된 오브젝트라고 생각하고 결국 자신의 메소드에 접근한다. #### 디폴트 의존관계 ##### 디폴트 의존관계를 갖는 빈 만들기 확장을 고려해서 기능을 분리하고, 인터페이스와 전략 패턴을 도입하고, DI를 적용한다면 늘어난 클래스와 인터페이스 구현과 의존관계 설정에 대한 부담은 감수해야 한다. 특정 의존 오브젝트가 대부분의 환경에서 거의 디폴트라고 해도 좋을 만큼 기본적으로 사용될 가능성이 있다면, 디폴트 의존관계를 갖는 빈을 만드는 것을 고려해볼 필요가 있다.
> 디폴트 의존관계란 외부에서 DI 받지 않는 경우 기본적으로 자동 적용되는 의존관계를 말한다.
* 생성자를 통한 디폴트 의존관계 설정 ``` public class DefaultSqlService extends BaseSqlService { public DefaultSqlService() { // 생성자에서 디폴트 의존 오브젝트를 직접 만들어서 스스로 DI 해준다. setSqlReader(new JaxbXmlSqlReader()); setSqlRegistry(new HashMapSqlRegistry()); } } ``` > DI 설정이 없을 경우 디폴트로 적용하고 싶은 의존 오브젝트를 생성자에서 넣어준다. DI란 클라이언트 외부에서 의존 오브젝트를 주입해주는 것이지만 이렇게 자신이 사용할 디폴트 의존 오브젝트를 스스로 DI 하는 방법도 있다. 이렇게 코드를 통해 의존관계의 오브젝트를 직접 주입해주면 특별히 DI가 필요한 상황이 아닌 대부분의 경우에는 편리하게 사용할 수 있다. 디폴트 의존 오브젝트를 사용하는 방법은 설정을 통해 다른 구현 오브젝트를 사용하게 해도 생성자에서 일단 디폴트 의존 오브젝트를 다 만들어버리는 단점이 있따. 이럴 땐 @PostConstruct 초기화 메소드를 이용해 프로퍼티가 설정됐는지 확인하고 없는 경우에만 디폴트 오브젝트를 만드는 방법이 있따. > 아무튼 이렇게 해서 디폴트 의존 오브젝트와 값을 이용해 설정을 간단하게 해주고 미리 준비된 기능을 손쉽게 사용할 수 있으면서도, 필요한 부분은 언제든지 확장 가능한 구조를 갖는 빈으로 만들었다.
### 서비스 추상화 적용 * 자바에는 다양한 XML과 자바오브젝트를 매핑하는 기술이 있다. 필요에 따라 다른 기술로 손쉽게 바꿔서 사용할 수 있게 해야 한다. * XML 파일을 좀 더 다양한 소스에서 가져올 수 있게 만든다. 임의의 클래스패스타 파일 시스템상의 절대위치 또는 HTTP 프로토콜을 통해 원격에서 가져오도록 확장할 수 있어야 한다. #### OXM 서비스 추상화 XML과 자바오브젝트를 매핑해서 상호 변환해주는 기술을 간단히 OXM(Object-XML Mapping)이라고 한다. OXM 프레임워크와 기술들은 기능 면에서 상호 호환성이 있다. 모두 사용 목적이 동일하기 때문에 유사한 기능과 API를 제공한다. 기능이 같은 여러 가지 기술이 존재한다는 이야기가 나오면 떠오르는 게 있다. 바로 서비스 추상화다. 로우레벨의 구체적인 기술과 API에 종속되지 않고 추상화된 레이어와 API를 제공해서 구현 기술에 대해 독립적인 코드를 작성할 수 있게 해주는 서비스 추상화가 필요하다. 스프링이 제공하는 OXM 추상 계층의 API를 이용해 XML 문서와 오브젝트 사이의 변환을 처리하게 하면, 코드 수정 없이도 OXM 기술을 자유롭게 바꿔서 적용할 수 있다. ##### OXM 서비스 인터페이스 스프링이 제공하는 OXM 추상화 서비스 인터페이스에는 자바오브젝트를 XML로 변환하는 Marshaller와, 반대로 XML을 자바오브젝트로 변환하는 Unmarshaller가 있다. * Unmarshaller 인터페이스 ``` package org.springframework.oxm; // spring-oxm 모듈 안에 정의되어 있다. ... import javax.xml.transform.Source; public interface Unmarshaller { boolean supports(Class<?> clazz); // 해당 클래스로 언마샬이 가능한지 확인해준다. 별로 사용할 일은 없다. Object unmarshal(Source source) throws IOException, XmlMappingException; // Source를 통해 제공받은 XML을 자바오브젝트 트리로 변환해서 그 루트 오브젝트를 돌려준다. } ``` > OXM 기술에 따라 Unmarshaller 인터페이스를 구현한 클래스가 있다. 각 클래스는 해당 기술에서 필요로 하는 추가 정보를 빈 프로퍼티로 지정할 수 있게 되어 있다. > 서비스 추상화는 로우레벨의 기술을 필요에 따라 변경해서 사용하더라도 일관된 애플리케이션 코드를 유지할 수 있게 해준다. #### OXM 서비스 추상화 적용 ##### 멤버 클래스를 참조하는 통합 클래스 ``` public class OxmSqlService implements SqlService { // OxmSqlService와 OxmSqlReader는 강하게 결합돼서 하나의 빈으로 등록되고 한 번에 설정할 수 있다. private final OxmSqlReader oxmSqlReader = new OxmSqlReader(); // final이므로 변경 불가능 하다. ... // private 멤버 클래스로 정의한다. 톱레벨 클래스인 OxmSqlService만이 사용할 수 있다. private class OxmSqlReader implements SqlReader { ... } } ``` > OxmSqlReader는 priavate 멤버 클래스이므로 외부에서 접근하거나 사용할 수 없다. 또한 OxmSqlService는 이를 final로 선언하고 직접 오브젝트를 생성하기 때문에 OxmSqlReader를 DI 하거나 변경할 수 없다. > 하나의 클래스로 만들어두기 때문에 빈의 등록과 설정은 단순해지고 쉽게 사용할 수 있다. #### 리소스 추상화 클래스패스 루트 등에 있는 XML 파일, 상대적인 클래스패스가 아니라 서버나 개발 시스템의 특정 폴더에 있는 파일, 서블릿 컨텍스트의 상대적인 폴더나 http, ftp 프로토콜로 접근할 수 있는 웹상의 리소스 파일을 가져오려면? 자바에는 이렇게 다양한 위치에 존재하는 리소스에 대한 단일화된 접근 인터페이스를 제공해주는 클래스가 없다. ##### 리소스 스프링은 자바에 존재하는 일관성 없는 리소스 접근 API를 추상화해서 Resource라는 추상화 인터페이스를 정의했다. * Resource 인터페이스 ``` public interface Resource extends InputStreamSource { // 리소스의 존재나 읽기 가능한지 여부를 확인할 수 있다. 또 현재 리소스에 대한 입력 스트림이 열려 있는지도 확인 가능하다. boolean exists(); boolean isReadable(); boolean isOpen(); // JDK의 URL, URI, File 형태로 전환 가능한 리소스에 사용된다. URL getURL() throws IOException; URI getURI() throws IOException; File getFile() throws IOException; Resource createRelative(String relativePath) throws IOException; // 리소스에 대한 이름과 부가적인 정보를 제공한다. long lastModified() throws IOException; String getFilename(); String getDescription(); } public interface InputStreamSource { InputStream getInputStream() throws IOException; // 모든 리소스는 InputStream형태로 가져올 수 있다. } ``` > 애플리케이션 컨텍스트가 사용할 설정정보 파일을 지정하는 것부터 시작해서 스프링의 거의 모든 API는 외부의 리소스 정보가 필요할 때는 항상 이 Resource 추상화를 이용한다. 다른 서비스 추상화의 오브젝트와는 달리, Resource는 스프링에서 빈이 아니라 값으로 취급된다. 리소스는 OXM이나 트랜잭션처럼 서비스를 제공해주는 것이 아니라 단순한 정보를 가진 값으로 지정된다. 그래서 추상화를 적용하는 방법이 문제다. ``````의 value 애트리뷰트에 넣는 방법밖에 없다. 하지만 value 애트리뷰트에 넣을 수 있는 건 단순한 문자열 뿐이다. ##### 리소스 로더 그래서 스프링에는 URL 클래스와 유사하게 접두어를 이용해 Resource 오브젝트를 선언하는 방법이 있다. 문자열 안에 리소스의 종류와 리소스의 위치를 함께 표현하게 해주는 것이다. 그리고 이렇게 문자열로 정의된 리소스를 실제 Resource 타입 오브젝트로 변환해주는 ResourceLoader를 제공한다. * ResourceLoader 인터페이스 ``` public interface ResourceLoader { Resource getResource(String location); // location에 담긴 스트링 정보를 바탕으로 ... 그에 적절한 Resource로 변환해준다. } ``` > 접두어가 없는 경우에는 리소스 로더의 구현 방식에 따라 리소스를 가져오는 방식이 달라진다. 하지만 접두어를 붙여주면 리소스 로더의 종류와 상관없이 접두어가 의미하는 위치와 방법을 이용해 리소스를 읽어온다. * ResourceLoader가 처리하는 접두어의 예 접두어 | 예 | 설명 :-----:|:----:|:----: file: | file:/C:/temp/file.txt | 파일 시스템의 C:/temp 폴더에 있는 file.txt를 리소스로 만들어준다. classpath: | classpath:file.txt | 클래스패스의 루트에 존재하는 file.txt 리소스에 접근하게 해준다. 없음 | WEN-INF/test.dat | 접두어가 없는 경우에는 ResourceLoader 구현에 따라 리소스의 위치가 결정된다. ServletResourceLoader라면 서블릿 컨텍스트의 루트를 기준으로 해석한다. http: | http://www.myserver.com/test.dat | HTTP 프로토콜을 사용해 접근할 수 있는 웹상의 리소스를 지정한다. ftp:도 사용할 수 있다. > ResourceLoader의 대표적인 예는 바로 스프링의 애플리케이션 컨텍스트다. 애플리케이션 컨텍스트가 구현해야 하는 인터페이스인 ApplicationContext는 ResourceLoader 인터페이스를 상속하고 있다. 따라서 모든 애플리케이션 컨텍스트는 리소스 로더이기도 하다. ##### Resource를 이용해 XML 파일 가져오기 Resource를 사용할 때는 Resource 오브젝트가 실제 리소스는 아니라는 점을 주의해야 한다. Resource는 단지 리소스에 접근할 수 있는 추상화된 핸들러일 뿐이다. 따라서 Resource 타입의 오브젝트가 만들어졌다고 해도 실제로 리소스가 존재하지 않을 수 있다. 기존의 sqlmapFile과 마찬가지로 sqlmap 리소스도 디폴트를 설정해준다. UserDao와 같은 클래스패스 안의 sqlmap.xml 파일을 클래스패스 리소스로 지정해주면 된다. 코드에서 클래스패스 리소스를 바로 지정하고 싶다면 ClassPathResource를 사용해 오브젝트를 만들면 된다. 반면에 문자열로 지정할 때는 리소스 로더가 인식할 수 있는 문자열로 표현해주면 된다. * classpath: 접두어를 이용해 지정한 리소스 ``` <bean id-"sqlService" class="springbook.user.sqlservice.OxmSqlService"> // classpath:는 디폴트이므로 생략 가능하다. 클래스패스 위치를 지정할 때는 클래스패스 루트부터 절대위치를 적어야 한다. </bean> ``` * file: 접두어를 이용해 지정한 리소스 ``` <bean id-"sqlService" class="springbook.user.sqlservice.OxmSqlService"> </bean> ``` * HTTP로 접근 가능한 리소스 ``` <bean id-"sqlService" class="springbook.user.sqlservice.OxmSqlService"> </bean> ``` > 스프링의 리소스 추상화를 이용하면 리소스의 위치와 접근 방법에 독립적인 코드를 쉽게 만들 수 있다. 스프링 애플리케이션에서 파일을 읽거나 참조하는 기능을 만들 때는 Resource 타입의 추상화 기능을 사용하면 된다.
### 인터페이스 상속을 통한 안전한 기능확장 #### DI와 기능의 확장 ##### DI를 의식하는 설계 DI를 적용하려면 커다란 오브젝트 하나만 존재해서는 안 된다. 최소한 두 개 이상의, 의존관계를 가지고 서로 협력해서 일하는 오브젝트가 필요하다. 그래서 적절한 책임에 따라 오브젝트를 분리해줘야 한다. DI는 런타임 시에 의존 오브젝트를 다이내믹하게 연결해줘서 유연한 확장을 꾀하는 게 목적이기 때문에 항상 확장을 염두에 두고 오브젝트 사이의 관계를 생각해야 한다. DI를 잘 활용할 수 있는 방법을 생각하면서 오브젝트를 설계한다면 객체지향 기술이 약속하는 유연한 확장과 재사용이 가능한 설계를 만드는 데 많은 도움이 될 것이다. ##### DI와 인터페이스 프로그래밍 DI를 적용할 때는 가능한 한 인터페이스를 사용하게 해야 한다. 물론 인터페이스를 사용하지 않고도 DI는 가능하다. 의존 오브젝트가 생성자나 수정자 등을 통해 주입만 가능하면 되기 때문에 의존 오브젝트의 클래스 타입을 클라이언트가 직접 사용해도 문제는 발생하지 않는다. 하지만 DI를 DI답게 만들려면 두 개의 오브젝트가 인터페이스를 통해 느슨하게 연결돼야 한다. 인터페이스를 사용하는 첫 번째 이유는 다형성을 얻기 위해서다. 하나의 인터페이스를 통해 여러 개의 구현을 바꿔가면서 사용할 수 있게 하는 것이 DI가 추구하는 첫 번째 목적이다. 그리고 인터페이스 분리 원칙을 통해 클라이언트와 의존 오브젝트 사이의 관계를 명확하게 해줄 수 있기 때문이다. 인터페이스를 클라이언트의 종류에 따라 적절하게 분리해서 오브젝트가 구현하게 하면 매우 유용하다. 오브젝트가 그 자체로 충분히 응집도가 높은 작은 단위로 설계됐더라도, 목적과 관심이 각기 다른 클라이언트가 있다면 인터페이스를 통해 이를 적절하게 분리해줄 필요가 있고, 이를 객체지향 설계 원칙에서는 **인터페이스 분리 원칙**이라고 부른다. #### 인터페이스 상속 하나의 오브젝트가 구현하는 인터페이스를 여러 개 만들어서 구분하는 이유 중의 하나는 오브젝트의 기능이 발전하는 과정에서 다른 종류의 클라이언트가 등장하기 때문이다. 때로는 인터페이스를 여러 개 만드는 대신 기존 인터페이스를 상속을 통해 확장하는 방법도 사용된다. 인터페이스 분리 원칙이 주는 장점은 모든 클라이언트가 자신의 관심에 따른 접근 방식을 불필요한 간섭 없이 유지할 수 있다는 점이다. 그래서 기존 클라이언트에 영향을 주지 않은 채로 오브젝트의 기능을 확장하거나 수정할 수 있다. 기존 클라이언트는 자신이 사용하던 인터페이스를 통해 동일한 방식으로 접근할 수만 있다면 오브젝트의 변경에 영향받지 않는다. 오브젝트가 완전히 새로운 인터페이스를 추가로 구현하는 경우뿐 아니라 기존 인터페이스를 상속해서 기능을 확장하는 경우에도 마찬가지다. > 인터페이스를 추가하거나 상속을 통해 확장하는 방식을 잘 활용하면 이미 기존의 인터페이스를 사용하는 클라이언트가 있는 경우에도 유연한 확장이 가능해진다. 오브젝트 사이의 의존관계와 목적에 따라 적절한 방식을 택하면 된다. 중요한 것은 클라이언트가 정말 필요한 기능을 가진 인터페이스를 통해 오브젝트에 접근하도록 만들었는가이다. > 잘 적용된 DI는 결국 잘 설계된 오브젝트 의존관계에 달려 있다. 인터페이스를 적절하게 분리하고 확장하는 방법을 통해 오브젝트 사이의 의존관계를 명확하게 해주고, 기존 의존관계에 영향을 주지 않으면서 유연한 확장성을 얻는 방법이 무엇인지 항상 고민해야 한다. DI와 객체지향 설계는 서로 밀접한 관계를 맺고 있다.
### DI를 이용해 다양한 구현 방법 적용하기 #### ConcurrentHashMap을 이용한 수정 가능 SQL 레지스트리 JDK의 HashMap으로는 멀티스레드 환경에서 동시에 수정을 시도하거나 수정과 동시에 요청하는 경우 예상하지 못한 결과가 발생할 수 있다. 멀티스레드 환경에서 안전하게 HashMap을 조작하려면 Collections.synchronizedMap() 등을 이용해 외부에서 동기화해줘야 한다. 하지만 이렇게 HashMap에 대한 전 작업을 동기화하면 DAO의 요청이 많은 고성능 서비스에서는 성능에 문제가 생긴다. 그래서 동기화된 해시 데이터 조작에 최적화되도록 만들어진 ConcurrentHashMap을 사용하는 방법이 일반적으로 권장된다. ConcurrentHashMap은 데이터 조작 시 전체 데이터에 대해 락을 걸지 않고 조회는 락을 아예 사용하지 않는다. 그래서 어느 정도 안전하면서 성능이 보장되는 동기화된 HashMap으로 이용하기에 적당하다. ##### 내장형 DB 스프링에는 팩토리 빈을 반드는 번거로운 작업을 대신해주는 전용 태그가 있다. 내장형 DB와 관련된 빈을 설정하고 등록해주는 기능이 있는 태그들은 jdbc 스키마에 정의되어 있다. jdbc 네임스페이스를 선언해두고 간단한 전용 태그로 빈을 정의해주면 내장형 DB를 손쉽게 사용할 수 있다. ``` ``` 이렇게 설정하면 embeddedDatabase 아이디를 가진 빈이 등록되며, 빈의 타입은 EmbeddedDatabase다.
### 스프링 3.1의 DI * 애노테이션의 메타정보 활용 * XML은 어느 환경에서나 손쉽게 편집이 가능하고, 내용을 변경하더라도 다시 빌드를 거칠 필요가 없다. 반면에 애노테이션은 자바 코드에 존재하므로 변경할 때마다 매번 클래스를 새로 컴파일해줘야 한다는 단점이 있다. 하지만 애노테이션은 애플리케이션을 핵심 로직을 담은 자바 코드와 이를 지원하는 IoC 방식의 프레임워크, 그리고 프레임워크가 참조하는 메타정보라는 세 가지로 구성하는 방식에 잘 어울리기 때문에 애노테이션의 활용이 늘어나고 있다. * 정책과 관례를 이용한 프로그래밍 * 애노테이션 같은 메타정보를 활용하는 프로그래밍 방식은 코드를 이용해 명시적으로 동작 내용을 기술하는 대신 코드 없이도 미리 약속한 규칙 또는 관례를 따라서 프로그램이 동작하도록 만드는 프로그래밍 스타일을 적극적으로 포용하게 만들어왔다.
> 스프링은 점차 애노테이션으로 메타정보를 작성하고, 미리 정해진 정책과 관례를 활용해서 간결한 코드에 많은 내용을 담을 수 있는 방식을 적극 도입하고 있다. #### 자바 코드를 이용한 빈 설정 * @Configuration - DI 정보로 사용될 자바 클래스 * @ImportResource("/test-applicationContext.xml") - 자바 클래스로 만들어진 DI 설정정보에서 XML의 설정정보를 가져오기 * @Bean - @Configuration이 붙은 DI 설정용 클래스에서 주로 사용되는 것으로, 메소드를 이용해서 빈 오브젝트의 생성과 의존관계 주입을 직접 자바 코드로 작성할 수 있다. 리턴 값 타입은 인터페이스로 하는 것이 좋다. * @Autowired - 이 애노테이션이 붙은 필드의 타입과 같은 빈이 있으면 해당 빈을 필드에 자동으로 넣어준다.(컨테이너가 주입해준다.) * @Resource - @Autowired와 유사하게 필드에 빈을 주입받을 때 사용한다. 차이점은 @Autowired는 필드의 타입을 기준으로 빈을 찾고 @Resource는 필드 이름을 기준으로 한다. * @EnableTransactionManagement - 스프링 3.1은 XML에서 자주 사용되는 전용 태그를 @Enable로 시작하는 애노테이션으로 대체할 수 있게 다양한 애노테이션을 제공한다. 가장 대표적으로 사용되는 것이 바로 @EnableTransactionManagement다. #### 빈 스캐닝과 자동와이어링 ##### @Autowired를 이용한 자동와이어링 @Autowired는 자동와이어링 기법을 이용해서 조건에 맞는 빈을 찾아 자동으로 수정자 메소드나 필드에 넣어준다. 자동와이어링을 이용하면 컨테이터가 이름이나 타입을 기준으로 주입될 빈을 찾아주기 때문에 빈의 프로퍼티 설정을 직접해주는 자바 코드나 XML의 양을 대폭 줄일 수 있다. 컨테이너가 자동으로 주입할 빈을 결정하기 어려운 경우도 있다. 이럴 땐 직접 프로퍼티에 주입할 대상을 지정하는 방법을 병행하면 된다. ``` public class UserDaoJdbc implements UserDao { @Autowired public void setDataSource(DataSource dataSource) { this.jdbcTemplate = new JdbcTemplate(dataSource); } } ``` 스프링은 @Autowired가 붙은 수정자 메소드가 있으면 파라미터 타입을 보고 주입 가능한 타입의 빈을 모두 찾는다. 여기서는 DataSource이므로 DataSource 타입의 빈을 모두 찾는다. 주입 가능한 빈이 하나라면 스프링이 수정자 메소드를 호출해서 넣어준다. 만약 두 개 이상이 나오면 그중에서 프로퍼티와 동일한 이름의 빈이 있는지 찾는다. 하나가 dataSource 빈이고 다른 하나가 embeddedDatabase빈이라면 둘중에 dataSource 빈이 수정자 메소드의 프로퍼티 이름과 일치하기 때문에 이를 넣어준다. 만약 타입과 이름을 모두 비교해도 최종 후보를 찾아내지 못하면 주입할 빈을 찾을 수 없다는 에러가 난다. ※ 단순히 필드에 값을 저장하는 수정자 메소드라도 @Autowired를 필드에 직접 부여했다고 메소드를 생략하면 안 되는 경우가 있다. 스프링 컨테이너에서 의존관계를 맺어주는 방식으로만 코드가 사용된다면 상관없지만 스프링과 무관하게 직접 오브젝트를 생성하고 다른 오브젝트를 주입해서 테스트하는 순수한 단위 테스트를 만드는 경우에는 수정자 메소드가 필요하다. > @Autowired와 같은 자동와이어링은 적절히 사용하면 DI 관련 코드를 대폭 줄일 수 있어서 편리하다. 반면에 빈 설정정보를 보고 다른 빈과 의존관계가 어떻게 맺어져 있는지 한눈에 파악하기 힘들다는 단점도 있긴 하다. ##### @Component를 이용한 자동 빈 등록 @Component는 스프링이 애노테이션에 담긴 메타정보를 이용하기 시작했을 때 @Autowired와 함께 소개된 대표적인 애노테이션이다. @Component는 클래스에 부여된다. @Component가 붙은 클래스는 빈 스캐너를 통해 자동으로 빈으로 등록된다. 정확히는 @Component 또는 @Component를 메타 애노테이션으로 갖고 있는 애노테이션이 붙은 클래스가 자동 빈 등록 대상이 된다. @Component 애노테이션이 달린 클래스를 자동으로 찾아서 빈을 등록해주게 하려면 빈 스캔 기능을 사용하겠다는 애노테이션 정의가 필요하다. 빈 자동등록이 컨테이너가 디폴트로 제공하는 기능은 아니기 때문이다. 프로젝트 내의 모든 클래스패스를 다 뒤져서 @Component 애노테이션이 달린 클래스를 찾는 것은 부담이 많이 가는 작업이다. 그래서 특정 패키지 아래서만 찾도록 기준이 되는 패키지를 지정해줄 필요가 있다. 이때 사용되는 애노테이션은 @ComponentScan이다. ``` @Configuration @EnableTransactionManagement @ComponentScan(basePackages="springbook.user") public class TestApplicationContext { ... } ``` > @ComponentScan의 basePackages 엘리먼트는 @Component가 붙은 클래스를 스캔할 기준 패키지를 지정할 때 사용한다. 기준 패키지는 여러 개 넣어도 된다. 지정한 패키지 아래의 모든 서브패키지를 다 검색한다. > @Component가 붙은 클래스가 발견되면 새로운 빈을 자동으로 추가한다. 빈의 클래스는 @Component가 붙은 클래스이고, 빈의 아이디는 따로 지정하지 않았으면 클래스 이름의 첫 글자를 소문자로 바꿔서 사용한다. * 자동 빈 등록 대상으로 만든 DAO 클래스는 데이터 액세스 서비스를 제공하는 DAO 빈이다. 스프링은 이런 DAO 빈을 자동등록 대상으로 만들 때 사용할 수 있게 @Repository 애노테이션을 제공한다. @Component를 부여하는 것만으로도 등록 대상으로 만드는 데 충분하지만 스프링은 DAO 기능을 제공하는 클래스에는 @Reposotory애노테이션을 이용하도록 권장한다. @Repository는 @Component를 메타 애노테이션으로 갖고 있다. * @Service도 @Repository처럼 스프링이 제공하는 빈 자동등록용 애노테이션인데, 이 애노테이션은 비즈니스 로직을 담고 있는 서비스 계층의 빈을 구분하기 위해 사용된다. 서비스 계층은 트랜잭션 경계가 되는 곳이라 @Transactional이 함께 사용되는 경우가 많다. > 자동와이어링과 자동등록 방식을 적용하면 DI와 관련된 코드나 설정정보가 간결해진다. #### 컨텍스트 분리와 @Import 성격이 다른 DI 정보를 분리한다. 하나 이상의 설정 클래스가 스프링 테스트에서 사용되게 하려면 classes에 적용할 설정 클래스를 모두 나열해주면 된다. 엘리먼트 이름을 보면 알겠지만 classes는 배열 타입이므로 클래스를 하나 이상 지정할 수 있다. ``` @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes={TextAppContext.class, AppContext.class}) public class UserDaoTest { ... } ``` ##### @Import 자바 클래스로 된 설정정보를 가져올 때는 @ImportResource 대신 @Import를 이용한다. ``` @Configuration @EnableTransactionManagement @ComponentScan(basePackages="springbook.user") @Import(SqlServiceContext.class) public class AppContext { ... } ``` > 테스트 코드의 @ContextConfiguration의 classes에 더이상 추가하지 않아도 된다. #### 프로파일 ##### @Profile과 @ActiveProfiles 스프링 3.1은 환경에 따라서 빈 설정정보가 달라져야 하는 경우에 파일을 여러 개로 쪼개고 조합하는 등의 번거로운 방법 대신 간단히 설정정보를 구성할 수 있는 방법을 제공한다. 실행환경에 따라 빈 구성이 달라지는 내용을 프로파일로 정의해서 만들어두고, 실행 시점에 어떤 프로파일의 빈 설정을 사용할지 지정하는 것이다. 프로파일은 설정 클래스 단위로 지정한다. ``` @Configuration @Profile("test") public class TestAppContext { ... } ``` @Profile이 붙은 설정 클래스는 @Import로 가져오든 @ContextConfiguration에 직접 명시하든 상관없이 현재 컨테이너의 활성(active) 프로파일 목록에 자신의 프로파일 이름이 들어 있지 않으면 무시된다. 활성 프로파일이란 스프링 컨테이너를 실행할 때 추가로 지정해주는 속성이다. 활성 프로파일로 test프로파일을 지정하려면 @ActiveProfiles 애노테이션을 사용하면 된다. ``` @RunWith(SpringJUnit4ClassRunner.class) @ActiveProfiles("test") @ContextConfiguration(classes=AppContext.class) public class UserDaoTest { ... } ``` ##### 컨테이너의 빈 등록 정보 확인 스프링 컨테이너는 모두 BeanFactory라는 인터페이스를 구현하고 있다. BeanFactory의 구현 클래스 중에 DefaultListableBeanFactory가 있는데 거의 대부분의 스프링 컨테이너는 이 클래스를 이용해 빈을 등록하고 관리한다. 스프링은 DefaultListableBeanFactory 오브젝트를 @Autowired로 주입받아서 이용하게 해준다. DefaultListableBeanFactory에는 getBeanDefinitionNames() 메소드가 있어서 컨테이너에 등록된 모든 빈 이름을 가져올 수 있고, 빈 이름을 이용해서 실제 빈과 빈 클래스 정보 등도 조회해볼 수 있다. * 등록된 빈 내역을 조회하는 테스트 메소드 ``` @Autowired DefaultListableBeanFactory bf; @Test public void beans() { for(String n + bf.getBeanDefinitionNames()) { System.out.println(n + " \t" + bf.getBean(n).getClass().getName()); } } ``` > 테스트를 해보면 프로파일이 적용됐음을 알 수 있다. ##### 중첩 클래스를 이용한 프로파일 적용 파일이 많아 전체 구성을 살펴보기가 번거롭고, 서로 의존관계를 맺고 있는 빈들이 많아진다면 어느 빈이 어디서 적용됐는지 확인하기 위해 여러 개의 클래스 파일을 열어보기가 불편하다. 따라서 프로파일에 따라 분리했던 설정정보를 하나의 파일로 모을 수 있다. 각각 독립적으로 사용될 수 있게 스태틱 클래스로 만들어 중첩하면 된다. 이렇게 하면 ```@Import(SqlServiceContext.class)```가 필요가 없어진다. 스태틱 중첩 클래스로 넣은 @Configuration클래스는 스프링이 자동으로 포함해주기 때문이다. > 각 프로파일 클래스에 빈 설정정보가 많다면 하나의 파일로 모았을 때 전체 구조를 파악하기가 힘들 수 있다. 따라서 그냥 파일을 분리하는 편이 나을 수도 있다. #### 프로퍼티 소스 ##### @PropertySource 프로퍼티에 들어갈 DB 연결정보는 텍스트로 된 이름과 값의 쌍으로 구성하면 된다. 복잡한 XML을 정의해서 사용할 것 없이 간단히 자바의 프로퍼티 파일 포맷을 이용하면 충분하다. 프로퍼티 파일의 확장자는 보통 properties이고, 내부에 키=값 형태로 프로퍼티를 정의한다. * database.properties 파일 ``` db.driverClass=com.mysql.jdbc.Driver db.url=jdbc:mysql://localhost/springbook?characterEncoding=UTF-8 db.username=spring db.password=book ``` 스프링 3.1은 빈 설정 작업에 필요한 프로퍼티 정보를 컨테이너가 관리하고 제공해준다. 스프링 컨테이너가 지정된 정보 소스로부터 프로퍼티 값을 수집하고, 이를 빈 설정 작업 중에 사용할 수 있게 해준다. 컨테이너가 프로퍼티 값을 가져오는 대상을 프로퍼티 소스(property source)라고 한다. 환경 변수나 시스템 프로퍼티처럼 디폴트로 프로퍼티 정보를 끌어오는 프로퍼티 소스도 있고, 프로퍼티 파일이나 리소스의 위치를 지정해서 사용되는 프로퍼티 소스도 있다. DB 연결정보는 database.properties라는 특정 파일에서 프로퍼티 값을 가져와야 하므로 프로퍼티 소스를 등록해줘야 한다. 프로퍼티 소스 등록에는 @PropertySource 애노테이션을 이용한다. * @PropertySource 적용 ``` @Configuration @EnableTransactionManagement @ComponentScan(basePackages="springbook.user") @Import(SqlServiceContext.class) @PropertySource("/database.properties") public class AppContext { ... } ``` @PropertySource로 등록한 리소스로부터 가져오는 프로퍼티 값은 컨테이너가 관리하는 Environment 타입의 환경 오브젝트에 저장된다. 환경 오브젝트는 빈처럼 @Autowired를 통해 필드로 주입받을 수 있다. 주입받은 Environment 오브젝트의 getProperty() 메소드를 이용하면 프로퍼티 값을 가져올 수 있다. ``` @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) { thorw new RuntimeException(e); } db.setUrl(env.getProperty("db.url")); ds.setUsername(env.getProperty("db.username")); ds.setPassword(env.getProperty("db.password")); return ds; } ``` Environment 오브젝트의 getProperty() 메소드는 프로퍼티 이름을 파라미터로 받아 스트링 타입의 프로퍼티 값을 돌려준다. url이나 username, password 등은 이 값을 그대로 사용하면 되는데, 문제는 driverClass 프로퍼티다. driverClass 프로퍼티는 DB 역ㄴ결 드라이버의 클래스로 클래스의 이름이 아니라 Class 타입의 클래스 오브젝트를 넘겨야 한다. 그래서 getProperty()로 가져온 드라이버 클래스 이름을 Class.forName() 메소드의 도움으로 Class 타입으로 변환한 뒤 사용해야 한다. 이때 클래스를 찾을 수 없다는 체크 예외가 발생할 수 있기 때문에 이를 다시 런타임 예외로 변환하는 등의 번거로운 작업이 필요하다. ##### PropertySourcesPlaceholderConfigurer dataSource 빈의 프로퍼티는 빈 오브젝트가 아니므로 @Autowired를 사용할 수는 없다. 대신 @Value 애노테이션을 사용하면 된다. @Value는 이름 그대로 값을 주입받을 때 사용한다. @Value의 사용 방법은 여러 가지가 있다. 컨테이너가 제공하는 프로퍼티 값을 주입받을 필드를 선언하고 앞에 @Value 애노테이션을 붙여준다. 그리고 @Value에는 프로퍼티 이름을 ${} 안에 넣은 문자열을 디폴트 엘리먼트 값으로 지정해준다. * 치환자(placeholder)를 이용한 값 주입(@Value에는 프로퍼티 이름을 ${} 안에 넣은 문자열을 디폴트 엘리먼트 값으로 지정해준다.) ``` @PropertySource("/database.properties") pulbic 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의 디폴트 값으로 넣은 ${db.driverClass}를 치환자라고 부르는 이유는 XML에서 ``````의 value에 사용하는 값 치환 방식과 유사하기 때문이다. @Value와 치환자를 이용해 프로퍼티 값을 필드에 주입하려면 특별한 빈을 하나 선언해줘야 한다. 프로퍼티 소스로부터 가져온 값을 @Value 필드에 주입하는 기능을 제공해주는 PropertySourcesPlaceholderConfigurer를 빈으로 정의해줘야 한다. ``` @Bean public static PropertySourcesPlaceholderConfigurer placeholderConfigurer() { return new PropertySourcesPlaceholderConfigurer(); } ``` 빈 팩토리 후처리기로 사용되는 빈을 정의해주는 것인데 이 빈 설정 메소드는 반드시 스태틱 메소드로 선언해야 한다. * @Value 필드를 사용하도록 수정한 dataSource() 메소드 ``` @Bean public DataSource dataSource() { SimpleDriverDataSource ds = new SimpleDriverDataSource(); ds.setDriverClass(this.driverClass); ds.setUrl(this.url); ds.setUsername(this.username); ds.setPassword(this.password); return ds; } ``` > @Value를 이용하면 driverClass처럼 문자열을 그대로 사용하지 않고 타입 변환이 필요한 프로퍼티를 스프링이 알아서 처리해준다는 장점이 있다. 지저분한 리플렉션 API나 try/catch가 없어서 깔끔하다. 반면에 dataSource 빈에서만 사용되는 프로퍼티인데 값을 주입받도록 클래스에 필드를 선언하는 것이 조금 부담이다. Environment를 이용해 프로퍼티 값을 가져오는 방법과 @Value를 이용하는 방법 중에서 작성하기 편하고 코드를 이해하기 쉽다고 생각되는 방법을 선택하면 된다. #### 빈 설정의 재사용과 @Enable* ##### 빈 설정자 SQL 매핑 리소스를 디폴트 위치와 다르게 만들려고 한다. 애플리케이션에 따라 SQL 매핑파일 이름이나 위치한 패키지를 변경할 수도 있고, 클래스패스가 아니라 서블릿 컨텍스트나 파일 시스템, HTTP를 통해 접근할 수 있는 리소스로 만들고 싶을 수도 있다. 결국 SQL 매핑 리소스는 빈 클래스 외부에서 설정할 수 있어야 한다. > 기본적인 DI를 이용하면 된다. 적용환경에 따라 바뀌는 부분은 인터페이스로 분리하고 DI를 통해 외부에서 주입되게 만들 수 있다. > 하지만 새로운 클래스를 하나 추가하는 것이 못마땅하다. @Configuration 애노테이션이 달린, 빈 설정으로 사용되는 클래스도 스프링에선 하나의 빈으로 취급된다. 그래서 빈의 자동와이어링에 쓰는 @Autowired를 이용할 수 있다. 따라서 인터페이스를 구현하는 새로운 클래스를 만들지 않고 빈 설정으로 사용되는 클래스가 직접 인터페이스를 구현하면 된다. 빈을 정의하고 DI 정보를 제공하는 설정용 클래스이면서 스스로도 빈으로 사용되는 것이다. ##### @Enable* 애노테이션 스프링 3.1은 모듈화된 빈 설정을 가져올 때 사용하는 @Import를 다른 애노테이션으로 대체할 수 있는 방법을 제공한다. @Component는 빈 자동등록 대상을 지정할 때 사용하는 애노테이션인데, 많은 경우 @Component를 직접 사용하기보다는 @Repository나 @Service처럼 좀 더 의미 있는 이름의 애노테이션을 만들어 사용한다. @Component를 메타 애노테이션으로 넣어서 애노테이션을 정의해주면 @Component와 동일한 빈 등록기능이 적용되면서 자동등록되는 빈의 종류나 계층이 무엇인지 나타낼 수도 있고, AOP를 이용해 특정 애노테이션이 달린 빈만 선정해 부가 기능을 제공하게 만들 수도 있다. 비슷한 방식으로 @Import도 다른 이름의 애노테이션으로 대체 가능하다. @Import애노테이션과 빈 설정 클래스 값을 메타 애노테이션으로 넣어서 애노테이션을 만들어주면 된다. * @Import를 메타 애노테이션으로 넣은 애노테이션 정의 ``` @Import(value=SqlServiceContext.class) public @interface EnableSqlService { } ``` > 새로 정의한 애노테이션 이름은 @EnableSqlService다. SqlService를 사용하겠다는 의미로 보면 된다. @Enable로 시작하는 대표적인 애노테이션은 XML의 ``````과 기능이 동일한 @EnableTransactionManagement가 있다. @EnableTransactionManagement 애노테이션도 @Import를 메타 애노테이션으로 갖고 있다. > @EnableTransactionManagement를 사용한다는 것은 결국 TransactionManagementConfigurationSelector 설정 클래스를 @Import 하는 셈이다.
### 정리 * SQL처럼 변경될 수 있는 텍스트로 된 정보는 외부 리소스에 담아두고 가져오게 만들면 편리하다. * 성격이 다른 코드가 한데 섞여 있는 클래스라면 먼저 인터페이스를 정의해서 코드를 각 인터페이스별로 분리하는 게 좋다. 다른 인터페이스에 속한 기능은 인터페이스를 통해 접근하게 만들고, 간단히 자기참조 빈으로 의존관계를 만들어 검증한다. 검증을 마쳤으면 아예 클래스를 분리해도 좋다. * 자주 사용되는 의존 오브젝트는 디폴트로 미리 정의해두면 편리하다. * XML과 오브젝트 매핑은 스프링의 OXM 추상화 기능을 활용한다. * 특정 의존 오브젝트를 고정시켜 기능을 특화하려면 멤버 클래스로 만드는 것이 편리하다. 기존에 만들어진 기능과 중복되는 부분은 위임을 통해 중복을 제거하는 게 좋다. * 외부의 파일이나 리소스를 사용하는 코드에서는 스프링의 리소스 추상화와 리소스 로더를 사용한다. * DI를 의식하면서 코드를 작성하면 객체지향 설계에 도움이 된다. * DI에는 인터페이스를 사용한다. 인터페이스를 사용하면 인터페이스 분리 원칙을 잘 지키는데도 도움이 된다. * 클라이언트에 따라서 인터페이스를 분리할 때, 새로운 인터페이스를 만드는 방법과 인터페이스를 상속하는 방법 두 가지를 사용할 수 있다. * 애플리케이션에 내장하는 DB를 사용할 때는 스프링의 내장형 DB 추상화 기능과 전용 태그를 사용하면 편리하다. 출처 : https://github.com/Masssidev/toby-vol1