스프링 프레임워크에서는 다음과 같은 디자인 패턴을 사용한다.
싱글톤 패턴은 객체에 대해 하나의 인스턴스만 존재하는 것을 보장하는 기법이다.
공유 자원을 관리하거나 로깅과 같은 횡단 서비스를 제공할 때 유용하다.
Spring IOC 컨테이너 당 하나의 객체로 싱글톤을 제한한다.
즉 Spring은 애플리케이션 컨텍스트 당 각 타입에 대해 하나의 빈만 생성한다.
애플리케이션은 두 개 이상의 스프링 컨테이너를 가질 수 있기 때문에 일반적인 싱글톤과는 다르다.
여러 컨테이너가 있는 경우 여러 객체가 애플리케이션에 존재할 수 있다.
@RestController
public class LibraryController {
@Autowired
private BookRepository repository; // singleton
@GetMapping("/count")
public Long findCount() {
System.out.println(repository);
return repository.count();
}
}
@RestController
public class BookController {
@Autowired
private BookRepository repository; // singleton
@GetMapping("/book/{id}")
public Book findById(@PathVariable long id) {
System.out.println(repository);
return repository.findById(id).get();
}
}
팩토리 메소드 패턴은 객체를 생성하기 위한 추상 메소드를 가지는 팩토리 클래스를 이용한다.
다음과 같이 같은 Vehicle이지만 Airplane, Boat 같이 특정 컨텍스트로 나누고 싶을 때 사용한다.
스프링은 DI 프레임워크에서 이 기법을 사용한다.
스프링은 bean 컨테이너를 bean을 생성하는 컨테이너로 사용한다.
따라서 스프링은 bean 팩토리 인터페이스를 bean 컨테이너의 추상화로 정의한다.
public interface BeanFactory {
getBean(Class<T> requiredType);
getBean(Class<T> requiredType, Object... args);
getBean(String name);
// ...
]
getBean 메소드는 팩토리 메소드에서 구현한다.
스프링은 ApplicationContext 인터페이스로 Bean Factory를 상속한다.
ApplicationContext 인터페이스는 추가적인 설정을 제공한다.
Spring은 이 설정과 외부 설정(xml 파일, 자바 어노테이션)을 사용하여 bean container를 시작한다.
AnnotationConfingApplicationContext
같은 ApplicationContext를 구현한 클래스를 사용하면 BeanFactory 인터페이스에서 상속된 다양한 getBean 메소드를 사용할 수 있다.
@Test
public void whenGetSimpleBean_thenReturnConstructedBean() {
ApplicationContext context =
new AnnotationConfigApplicationContext(ApplicationConfig.class);
Foo foo = context.getBean(Foo.class);
assertNotNull(foo);
}
@Test
public void whenGetPrototypeBean_thenReturnConstructedBean() {
String expectedName = "Some name";
ApplicationContext context =
new AnnotationConfigApplicationContext(ApplicationConfig.class);
Bar bar = context.getBean(Bar.class, expectedName);
assertNotNull(bar);
assertThat(bar.getName(), is(expectedName));
}
이 패턴은 다용도다.
외부 구성을 기반으로 애플리케이션의 동작을 완전히 변경할 수 있다.
애플리케이션의 오브젝트들의 구현사항을 바꾸길 원하면 ApplicationContext를 조정하면 된다.
@Test
public void whenGetPrototypeBean_thenReturnConstructedBean() {
String expectedName = "Some name";
ApplicationContext context = new AnnotationConfigApplicationContext(ApplicationConfig.class);
Bar bar = context.getBean(Bar.class, expectedName);
assertNotNull(bar);
assertThat(bar.getName(), is(expectedName));
}
@Test
public void givenXmlConfiguration_whenGetPrototypeBean_thenReturnConstructedBean() {
String expectedName = "Some name";
ApplicationContext context = new ClassPathXmlApplicationContext("context.xml");
// Same test as before ...
}
프록시 패턴은 프록시가 진짜 객체에 대한 액세스를 제어할 수 있게하는 기술이다.
프록시를 만들기 위해 진짜 객체와 동일한 인터페이스를 구현하고 진짜 객체에 대한 참조를 포함하는 객체를 만든다.
그러면 프록시를 진짜 객체 대신에 사용할 수 있다.
스프링에서 빈들은 더 근원적인 빈들에 대한 프록시다.
트랜잭션을 사용할 때 위와 같은 접근을 볼 수 있다.
@Service
public class BookManager {
@Autowired
private BookRepository repository; // bean
@Transactional
public Book create(String author) {
System.out.println(repository.getClass().getName());
return repository.create(author);
}
}
@Transactional
어노테이션은 스프링이 사용자가 만든 메소드를 실행하도록 지시한다.
프록시가 없다면 BookRepository 빈에 대한 접근을 제한하거나, 트랜잭션의 일관성을 보장할 수 없다.
Spring은 프록시를 만들어 BookRepository를 감싼 후 빈을 지시해 메소드를 실행하도록 한다.
BookManger의 create 메소드를 실행하면 다음과 같은 출력을 볼 수 있다.
com.baeldung.patterns.proxy.BookRepository$$EnhancerBySpringCGLIB$$3dc2b55c
BookRepository 오브젝트 ID가 아인 CGLIB 개체 ID가 표시된다.
스프링은 BookRepository 개체를 EnhancedBySpringCGLIB 오브젝트로 내부에 래핑했다.
일반적으로 스프링은 두개의 프록시를 사용한다.
기저 프록시를 노출하기 위해 트랜잭션을 사용하지만, 스프링은 빈에 대한 접근을 제어해야 하는 모든 시나리오에 프록시를 사용한다.
많은 프레임워크에서 코드의 중요한 부분은 공통 부분이다.
예를 들어 DB에 쿼리를 질의할 때는 다음과 같은 동일 단계를 거쳐야한다.
이러한 공통 단계는 템플릿 메소드 패턴의 이상적인 시나리오다.
템플릿 메소드 패턴은 일부 작업에 필요한 단계를 정의하는 기술로, 공통 단계를 구현하고 사용자 정의 가능한 단계를 추상적으로 유지한다.
하위 클래스에서 추상 클래스를 구현하고 사용자 정의 단계에 대한 구체적인 구현을 제공할 수 있다.
public abstract DatabaseQuery {
public void execute() {
Connection connection = createConnection();
executeQuery(connection);
closeConnection(connection);
}
protected Connection createConnection() {
// Connect to database...
}
protected void closeConnection(Connection connection) {
// Close connection...
}
protected abstract void executeQuery(Connection connection);
}
또는 콜백 방법을 제공하여 추상화에 단계를 제공할 수 있다.
콜백은 클라이언트에게 원하는 작업이 완료되었음을 알리는 방법이다.
결과 매핑과 같은 작업을 콜백으로 수행할 수 있다.
executeQuery
메소드를 사용하는 대신 콜백을 제공할 수 있다.
DatabaseQuery
클래스를 다음처럼 콜백을 이용하도록 변경한다.
이 콜백 매커니즘은 Spring이 JdbcTemplate 클래스에 사용하는 접근 방식이다.
public abstract DatabaseQuery {
public <T> T execute(String query, ResultsMapper<T> mapper) {
Connection connection = createConnection();
Results results = executeQuery(connection, query);
closeConnection(connection);
return mapper.map(results);
]
protected Results executeQuery(Connection connection, String query) {
// Perform query...
}
}
JDBC 데이터베이 관리 외에도 Spring은 다음과 같은 템플릿을 사용한다.
참고