0. 스프링이란?
- 스프링은 스프링 컨테이너 또는 애플리케이션 컨텍스트라고 불리는 스프링 런타임 엔진을 제공한다.
- 스프링 컨테이너는 설정정보를 참고로 해서 애플리케이션을 구성하는 오브젝트를 생성하고 관리한다.
프로그래밍 모델
- Ioc/DI는 오브젝트의 생명주기와 의존관계에 대한 프로그래밍 모델이다
- 서비스 추상화. 특정 기술과 환경에 에 종속되지 않도록 유연한 추상 계층을 두는 방법을 제공한다
- AOP. 애플리케이션 코드에 산재해서 나타나는 부가적인 기능을 독립적으로 모듈화하는 프로그래밍 모델이다.
01. 오브젝트와 의존관계
- 디폴트 생성자: 자바빈은 파라미터가 없는 디폴트 생성자를 갖고 있어야 한다. 툴이나 프레임워크에서 리플렉션을 이용해 오브젝트를 생성하기 때문에 필요하다
- 프로퍼미: 자바빈이 노출하는 이름을 가진 속성을 프로퍼티라고 한다. 프로퍼티는 setter와 getter를 이용해 수정 또는 조회할 수 있다.
상속을 사용하여 관심사를 분리할 수 있지만 상속을 이용하면 단점이 있다. 일단 자바는 다중 상속을 지원하지 않는데 관심사를 분리해야 하는 대상이 다른 클래스를 상속하고 있을 수 있다. 또한 상속관계는 부모-자식 간의 긴밀한 결합을 허용한다. 서브클래스는 슈퍼클래스의 기능을 직접 사용할 수 있고 슈퍼클래스의 변경이 있을 경우 서브클래스도 함께 수정 또는 개발이 이루어져야 한다.
개발 폐쇄 원칙은 '클래스나 모듈은 확장에는 열려 있어야 하고 변경에는 닫혀 있어야 한다'는 원칙이다. 클래스나 모듈은 확장은 손쉽게 가능하지만 변경에 대한 코드 변경은 있지 않아야 한다는 원칙이다. 개방 폐쇄 원칙은 높은 응집도와 낮은 결합도가 만족되어야 한다. 높은 응집도 라는 것은 변화가 일어날 때 해당 모듈에서 변하는 부분이 크다는 의미이다. 낮은 결합도란 책임과 관심사가 다른 오브젝트 또는 모듈과는 느슨하게 연결된 형태를 유지해야 한다는 것이다. 느슨하다는 것은 특정 클래스에 대한 연결이 아닌 인터페이스로 연결된다는 의미이다.
전략 패턴은 자신의 기능 맥락(context)에서 필요에 따라 변경이 필요한 알고리즘을 인터페이스를 통해 외부로 분리시키고, 이를 구현한 구체적인 알고리즘 클래스를 필요에 따라 바꿔서 사용할 수 있게하는 디자인 패턴이다. 즉 Dao클래스에서(context) DB커넥션을 구하는 알고리즘을 외부로 분리한 뒤 필요에 따라 DB커넥션을 구하는 알고리즘을 변경하며 Dao에서 사용되도록 하는 패턴이다.
제어관계 역전(Ioc)은 일반적인 프로그램의 흐름과는 다른 개념이다. 일반적인 프로그램은 main 메소드에서 필요한 오브젝트를 생성하고 오브젝트의 메소드를 호출하는 식이다. 즉 main 메소드에서 필요한 오브젝트를 '선택하여 사용'하는 것인데 제어 역전에서는 오브젝트가 자신이 사용할 오브젝트를 스스로 선택하지 않고 생성도하지 않는다. 모든 제어 권한을 자신이 아닌 다른 대상에게 위임하기 때문이다. 스프링이 제어권을 가지고 만들고 관계를 부여하는 오브젝트를 빈(bean)이라고 부른다.
간단한 용어 정리.
빈: 스프링이 Ioc 방식으로 관리하는 오브젝트
- 빈 팩토리: 스프링의 Ioc를 담당하는 핵심 컨테이너를 가리킴. 빈을 등록하고, 생성하고, 조회하고 반환하고, 그 외에 부갖거인 빈을 관리하는 기능을 담당
애클리케이션 컨텍스트: 빈 팩토리를 확장한 Ioc 컨테이너이다. 기본적인 기능은 빈 팩토리와 동일하지만 스프링이 제공하는 각종 부가 서비스를 추가로 제공한다
설정정보/메타정보: 스프링의 설정정보란 애플리케이션 컨텍스트 또는 빈 팩토리가 Ioc를 적용하기 위해 사용하는 메타정보를 말함
- 컨테이너 또는 Ioc컨테이너: Ioc 방식으로 빈을 관리하다는 의미에서 빈 팩토리나 애플리케이션 컨텍스트를 컨테이너 또는 Ioc컨테이너라고 한다
- 스프링 프레임워크: Ioc 컨테이너, 애플리케이션 컨텍스트를 포함해서 스프링이 제공하는 모든 기능을 통틀어 말함.
애플리케이션 컨텍스트는 빈을 싱글톤으로 관리한다. 따라서 빈 내의 인스턴스 변수는 읽기전용이 아니라면 매우매우 위험하다. 스프링은 보통 멀티스레드 환경에서 사용되는데 다양한 사용자가 싱글톤 오브젝트를 사용하여 변경 작업을 했을 때 인스턴스 변수를 사용했을 경우 여러 사용자의 요청이 혼합되어 변경이 될 것이다. 따라서 빈 내에서는 읽기전용일 경우에만 인스턴스 변수로 가지고 그 외에는 파라미터나 로컬변수 리턴값을 이용해야 한다.
의존관계는 두 개의 클래스 또는 모듈이 의존관계에 있다고 말할 떄는 항상 방향성을 부여해줘야 한다. A가 B에 의존하고 있다는 것은 B가 변하면 A에 영향이 있다는 것이다. 반면 B는 A의 변화에 영향을 받지 않는다. 인터페이스에 대해서만 의존관계를 만들어두면 인터페이스 구현 클래스와의 관계는 느슨해지면서 변화에 영향을 덜 받는 상태가 된다. 의존관계 주입은 런타임 시에 사용할 오브젝트(의존 오브젝트)를 클라이언트와 연결해주는 작업을 말한다.
의존관계 주입(DI)의 장점
- 인터페이스를 통해 결합도가 낮은 코드
- 확장에는 열려있고(인터페이스를 통한 주입) 변경에는 닫혀 있다.
02. 테스트
스프링의 가장 중요한 가치는 객체지향과 테스트라고 한다. 그렇다면 굳이 테스트를 작성해야 하는 이유가 뭘까?
- 웹을 통한 테스트를 한다면 하나의 클래스를 테스트하기 위해 서비스 계층, MVC 프레젠테이션 계층까지 포함한 모든 입출력 기능을 만들어야 한다. 스프링의 테스트를 이용하면 테스트할 데이터도 코드로 작성하고 결과도 바로바로 확인할 수 있다.
- 큰 단위의 기능에 문제가 생겼을 경우 작은 단위의 테스트가 작성되어 있다면 문제 해결이 쉬워진다.
테스트를 작성하는데 주의해야 할 점
- 외부 환경에 영향을 받지 않는 테스트를 작성해야 한다.
- 테스트 실행 순서에 상관없이 동일한 결과를 반환해야 한다.
TDD란 만들고자 하는 기능의 내용을 담고 있으면서 만들어진 코드를 검증도 해줄 수 있도록 테스트 코드를 먼저 만들고, 테스트를 성공하게 해주는 코드를 작성하는 방식의 개발 방법이다. TDD의 기본 원칙은 "실패한 테스트를 성공시키기 위한 목적이 아닌 코드는 만들지 않는다"는 것이다.
테스트를 수행하는 데 필요한 정보나 오브젝트를 픽스처라고 한다. 일반적으로 픽스처는 여러 테스트에서 반복저ㅏㄱ으로 사용되기 때문에 @Before 메소드를 이용해서 생성하면 편리하다.
@Before 메소드는 @Test 메소드를 실행하기 전에 실행되며 각 @Test 메소드가 실행될 때마다 매번 실행된다. 그러나 @BeforeClass 스태틱 메소드를 클래스 전체에 걸쳐 단 한번만 실행된다. 하지만 보통 스프링이 직접 제공하는 애플리케이션 컨텍스트 테스트 지원기능을 사용하는 것이 더 편리하다.
1 2 3 4 5 6 7 | @RunWith(SpringJUnit4ClassRunner.class) // 스프링의 테스트 컨텍스트 프레임워크의 JUnit 확장기능 @ContextConfiguration(locations="/applicationContext.xml") // 테스트 컨텍스트가 자동으로 만들어줄 애플리케이션 컨텍스트의 위치 지정 public class UserDaoTest { // 테스트 오브젝트가 만들어지고 나면 스프링 테스트 컨텍스트에 의해 자동으로 값이 주입됨 @Autowired private ApplicationContext context; } | cs |
03. 템플릿
04. 예외
런타임 예외 중심의 전략은 낙관적인 예외처리 기법이라고 할 수 있다. 일단 복구할 수 있는 예외는 없다고 가정하고 예외가 생겨도 어차피 런타임 예외이므로 시스템 레벨에서 알아서 처리해 줄 것이고 꼭 필요한 경우는 런탕미 예외라도 잡아서 복구하거나 대응할 수 있다. 반면에 시스템 또는 외부의 예외상황이 원인이 아니라 애플리케이션 자체의 로직에 의해 의도적으로 발생시키고 반드시 catch 해서 무엇인가 조치를 취하도록 요구하는를 애플리케이션 예외라고 한다. 애플리케이션 예외는 체크 예외로 만들어서 예외상황에 대한 로직을 강제로 구현하도록 하는 것이 좋다.
SQL Exception과 같이 코드레벨에서 대응이 불가능한 예외는 필요도 없는 기계적인 throws 선언이 등장하도록 방치하지 말고 가능한 한 빨리 언체크/런타임 예외로 전환해줘야 한다.
체크 예외와 언체크 예외를 구분하는 기준은 코드 레벨에서 복구가능한지 여부로 결정하거나 또는 애플리케이션 로직에 영향을 줄 수 있으므로 예외 상황에 대해 강제로 인식이 필요할 때 인듯함
- 예외를 잡아서 아무런 조취를 취하지 않거나 의미 없는 throws 선언을 남발하는 것은 위험하다
- 예외는 복구하거나 예외처리 오브젝트로 의도적으로 전달하거나 적절한 예외로 전환해야 한다.
- 좀 더 의미 있는 예외로 변경하거나, 불필요한 catch/throws를 피하기 위해 런타임 예외로 포장하는 두가지 방법의 예외전환이 있다.
- 복구할 수 없는 에외는 가능한 한 빨리 런타임 예외로 전환하는 것이 바람직하다.
- 애플리케이션의 로직을 담기 위한 예외는 체크 예외로 만든다
- JDBC의 SQL Exception은 대부분 복구할 수 없는 예외이므로 런타임 예외로 포장해야한다.
- SQL Exception의 에러 코드는 DB에 종속되기 때문에 DB에 독립적인 예외로 전환될 필요가 있다.
- 스프링은 DataAccessException을 통해 DB에 독립적으로 적용 가능한 추상화된 런타임 예외 계층을 제공한다.
- DAO를 데이터 엑세스 기술에서 독립시켜려면 인터페이스 도입과 런타임 예외 전환, 기술에 독립적인 추상화된 예외로 전환이 필요하다.
05. 서비스 추상화
- 비즈니스 로직을 담은 코드는 데이터 엑세스 로직을 담은 코드와 깔끔하게 분리되는 것이 바람직하다. 비즈니스 로직 코드 또한 내부적으로 책임과 역할에 따라서 깔끔하게 메소드로 분리되어야 한다.
- 이를 위해서는 DAO의 기술 변화에 서비스 계층의 코드가 영향을 받지 않도록 인터페이스와 DI를 활용해서 결합도를 낮춰야 한다.
- DAO를 사용하는 비즈니스 로직에는 단위 작업을 보장해주는 트랜잭션이 필요하다
- 트랜잭션의 시작과 종료를 지정하는 일은 트랜잭션 경계설정이라고 한다. 트랜잭션 경계설정은 주로 비즈니스 로직안에서 일어나는 경우가 많다.
- 시작된 트랜잭션 정보를 담은 오브젝트를 파라미터로 dao에 전달하는 방법은 매우 비효율적이기 때문에 스프링이 제공하는 트랜잭션 동기화 기법을 활용하는 것이 편리하다.
- 자바에서 사용되는 트랜잭션 API의 종류와 방법은 다양하다. 환경과 서버에 따라서 트랜잭션 방법이 변경되면 경계설정 코드도 함께 변경돼야 한다.
- 트랜잭션 방법에 따라 비즈니스 로직을 담은 코드가 함께 변경되면 단일 책임 원칙에 위배되며, DAO가 사용하는 특정 기술에 대해 강한 결합을 만들어낸다.
- 트랜잭션 경계설정 코드가 비즈니스 로직 코드에 영향을 주지 않게 하려면 스프링이 제공하는 트랜잭션 서비스 추상화를 이용하면 된다.
- 서비스 추상화는 로우레벨의 트랜잭션 기술과 api의 변화에 상관없이 일관된 API를 가진 추상화 계층을 도입한다.
- 서비스 추상화는 테스트하기 어려운 JavaMail 같은 기술에도 적용할 수 있다. 테스트를 편리하게 작성하도록 도와주는 것만으로도 서비스 추상화는 가치가 있다.
- 테스트 대상이 사용하는 의존 오브젝트를 대체할 수 있도록 만든 오브젝트를 테스트 대역이라고 한다.
- 테스트 대역은 테스트 대상 오브젝트가 원할하게 동작할 수 있도록 도우면서 테스트를 위해 간접적인 정보를 제공해주기도 한다.
- 테스트 대역 중에서 테스트 대상으로부터 전달받은 정보를 검증할 수 있도록 설계된 것을 목 오브젝트라고 한다.