Table of Contents

개요#

1장은 스프링이 어떤것이고 무엇을 제공하는지보다는 스프링이 관심을 갖는 대상인 오브젝트의 설계와 구현, 동작원리에 더 집중하기를 바란다.

1.1. 초난감 DAO#

1.1.1 User#

User는 사용자의 정보를 저장하는 자바빈 클래스이다. String타입의 id, name, password 필드를 가진다.

1.1.2. UserDao#

사용자 정보를 관리하는 Dao클래스이다. JDBC를 이용할 경우 대개 다음과 같은 순서를 따른다.

  1. DB연결을 위한 Connection을 가져온다.
  2. SQL을 담은 Statement를 만든다.
  3. 만들어진 Statement를 실행한다.
  4. 조회의 경우 SQL쿼리의 실행결과를 ResultSet으로 받아서 정보를 저장할 오브젝트에 옮겨준다.
  5. 작업중에 생성된 Connection, Statement, ResultSet같은 리소스는 작업을 마친후 반드시 닫아준다.
  6. JDBC API가 만들어내는 예외를 잡아서 직접 처리하거나 메소드에 throws를 선언해서 예외가 발생하면 메소드 밖으로 던지게 한다.
package springbook.user.dao;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import springbook.user.domain.User;

public class UserDao {
	public void add(User user) throws ClassNotFoundException, SQLException {
		Class.forName("com.mysql.jdbc.Driver");
		Connection c = DriverManager.getConnection("jdbc:mysql://localhost/springbook?characterEncoding=UTF-8", "spring",
				"book");

		PreparedStatement ps = c.prepareStatement(
			"insert into users(id, name, password) values(?,?,?)");
		ps.setString(1, user.getId());
		ps.setString(2, user.getName());
		ps.setString(3, user.getPassword());

		ps.executeUpdate();

		ps.close();
		c.close();
	}


	public User get(String id) throws ClassNotFoundException, SQLException {
		Class.forName("com.mysql.jdbc.Driver");
		Connection c = DriverManager.getConnection("jdbc:mysql://localhost/springbook?characterEncoding=UTF-8", "spring",
				"book");
		PreparedStatement ps = c
				.prepareStatement("select * from users where id = ?");
		ps.setString(1, id);

		ResultSet rs = ps.executeQuery();
		rs.next();
		User user = new User();
		user.setId(rs.getString("id"));
		user.setName(rs.getString("name"));
		user.setPassword(rs.getString("password"));

		rs.close();
		ps.close();
		c.close();

		return user;
	}

}

1.1.3. main()을 이용한 DAO테스트 코드#

1.2. DAO의 분리#

1.2.1. 관심사의 분리#

  • 향후 발생할 수 있는 요구사항 변경에 대비하자.
  • 변화의 폭을 최소한으로 줄여주자. (-> 분리와 확장을 고려한 설계)
    • DB접속 패스워드를 변경하는데 모든 클래스를 변경해야 한다면?
    • 트랜잭션 방법을 변경하기 위해 코드를 모두 변경해야 한다면?
관심사의 분리
관심이 같은 것끼리 하나의 객체안으로 또한 친한 객체로 모이게하고 관심이 다른 것은 가능한 따로 떨어져서 서로 영향을 주지 않도록 분리한다.

1.2.2. 커넥션 만들기의 추출#

UserDao의 관심사항#

  1. DB와 연결을 위한 커넥션을 어떻게 가져올까라는 관심이다. DB의 종류, 드라이버, 로그인 정보등이 다를수도 있다.
  2. 사용자 등록을 위해 DB에 보낼 SQL문장을 담을 Statement를 만들고 실행하는 것이다. 사용자 정보를 Statement에 바인딩하고 Statement에 담긴 SQL을 DB를 통해 실행시키는 방법이다.
  3. 작업이 끝나면 사용한 리소스인 Statement와 Connection오브젝트를 닫아줘서 소중한 공유리소스를 시스템에 돌려주는 것이다.

중복코드의 메소드 추출#

public class UserDao {
	public void add(User user) throws ClassNotFoundException, SQLException {
		Connection c = getConnection();
		.....
	}

	public User get(String id) throws ClassNotFoundException, SQLException {
		Connection c = getConnection();
		.....
	}

	private Connection getConnection() throws ClassNotFoundException, SQLException {
		Class.forName("com.mysql.jdbc.Driver");
		Connection c = DriverManager.getConnection(
			"jdbc:mysql://localhost/springbook?characterEncoding=UTF-8", 
			"spring", "book");
		return c;
	}
}

변경사항에 대한 검증: 리팩토링과 테스트#

getConnection() 메소드처럼 공통된 코드를 별도의 메소드로 추출하는 리팩토링 기법을 메소드 추출(extract method)이라고 한다.

1.2.3. DB커넥션 만들기의 독립#

상속을 통한 확장#

그림 1-1 클래스다이어그램
public abstract class UserDao {
	public void add(User user) throws ClassNotFoundException, SQLException {
		Connection c = getConnection();
		.....
	}

	public User get(String id) throws ClassNotFoundException, SQLException {
		Connection c = getConnection();
		.....
	}

	private abstract Connection getConnection() throws ClassNotFoundException, SQLException;
}

public class NUserDao extends UserDao {
	private Connection getConnection() throws ClassNotFoundException, SQLException {
		// N사 DB Connection 생성코드
	}
}

public class DUserDao extends UserDao {
	private Connection getConnection() throws ClassNotFoundException, SQLException {
		// D사 DB Connection 생성코드
	}
}
  • 기능구분
    • 데이터를 제어하는 부분에 대해서는 UserDao가 담당
    • DB연결방법에 대해서는 각각의 하위 클래스인 NUserDao, DUserDao가 담당
  • 상속을 통한 확장의 단점
    • 자바는 다중상속을 지원하지 않기 때문에 이미 해당 클래스가 다른 클래스를 상속하고 있다면 이 방법을 사용하기 힘듬
    • 상속은 생각보다 관계가 밀접해서 향후 개선을 하거나 기능을 추가할때 제약이 될수도 있음
템플릿 메소드 패턴
상위 클래스에서 기본적인 로직의 흐름을 만들고 기능의 일부를 하위 클래스에 위임하도록 추상메소드나 오버라이딩을 사용하는 디자인 패턴, 스프링에서 많이 사용한다.
팩토리 메소드 패턴
서브클래스에서 구체적인 오브젝트 생성방법을 결정하게 하는 디자인 패턴
메소드 패턴
템플릿 메소드 패턴과 팩토리 메소드 패턴에 대해서 조금더 정리할 필요가 있음 책 69페이지에 추가 설명도 있음. 참고
그림 1-2 클래스다이어그램

1.3. DAO의 확장#

1.3.1. 클래스의 분리#

그림 1-3 클래스다이어그램
public class UserDao {
	private SimpleConnectionMaker simpleConnectionMaker;
	
	public UserDao() {
		this.simpleConnectionMaker = new SimpleConnectionMaker();
	}

	public void add(User user) throws ClassNotFoundException, SQLException {
		Connection c = this.simpleConnectionMaker.getConnection();
		.....
	}

	public User get(String id) throws ClassNotFoundException, SQLException {
		Connection c = this.simpleConnectionMaker.getConnection();
		.....
	}
}

public class SimpleConnectionMaker {
	public Connection getConnection() throws ClassNotFoundException,
			SQLException {
		Class.forName("com.mysql.jdbc.Driver");
		Connection c = DriverManager.getConnection(
				"jdbc:mysql://localhost/springbook?characterEncoding=UTF-8", "spring", "book");
		return c;
	}
}
  • 확장구조를 제거했기 때문에 앞서 언급한 확장구조의 단점은 사라졌다.
  • SimpleConnectionMaker 가 커넥션을 생성하는 기능을 하는데 커넥션을 생성하는 다른 클래스를 사용하려면 UserDao에서 이 코드를 변경해야 한다.
  • 커넥션을 생성하는 메소드명이 정해져있어서 메소드명이 바뀐다면 UserDao의 코드를 변경해야 한다.
  • 결국에 클래스 분리로 상속의 단점을 해결했지만 고정된 코드로 인해 변경에 어려움을 주게 된 셈이다.

1.3.2. 인터페이스의 도입#

그림 1-4 클래스다이어그램
public interface ConnectionMaker {
	public abstract Connection makeConnection() throws ClassNotFoundException,
			SQLException;
}

public class DConnectionMaker implements ConnectionMaker {
	public Connection makeConnection() throws ClassNotFoundException,
			SQLException {
		Class.forName("com.mysql.jdbc.Driver");
		Connection c = DriverManager.getConnection(
				"jdbc:mysql://localhost/springbook?characterEncoding=UTF-8", "spring", "book");
		return c;
	}
}

public class UserDao {
	private ConnectionMaker connectionMaker;
	
	public UserDao() {
		this.connectionMaker = new DConnectionMaker();
	}

	public void add(User user) throws ClassNotFoundException, SQLException {
		Connection c = this.connectionMaker.makeConnection();
		.....
	}

	public User get(String id) throws ClassNotFoundException, SQLException {
		Connection c = this.connectionMaker.makeConnection();
		.....
	}
}
  • 여전히 UserDao생성자를 보면 this.connectionMaker = new DConnectionMaker(); 코드가 있다.

1.3.3. 관계설정 책임의 분리#

그림 1-5 클래스다이어그램
public interface ConnectionMaker {
	public abstract Connection makeConnection() throws ClassNotFoundException,
			SQLException;
}

public class DConnectionMaker implements ConnectionMaker {
	public Connection makeConnection() throws ClassNotFoundException,
			SQLException {
		Class.forName("com.mysql.jdbc.Driver");
		Connection c = DriverManager.getConnection(
				"jdbc:mysql://localhost/springbook?characterEncoding=UTF-8", "spring", "book");
		return c;
	}
}

public class UserDao {
	private ConnectionMaker connectionMaker;
	
	public UserDao() {
		this.connectionMaker = simpleConnectionMaker;
	}

	public void add(User user) throws ClassNotFoundException, SQLException {
		Connection c = this.connectionMaker.makeConnection();
		.....
	}

	public User get(String id) throws ClassNotFoundException, SQLException {
		Connection c = this.connectionMaker.makeConnection();
		.....
	}
}

public class UserDaoTest {
	public static void main(String[] args) throws ClassNotFoundException, SQLException {
		ConnectionMaker connectionMaker = new DConnectionMaker();
		UserDao dao = new UserDao(connectionMaker);
		.....
	}
}
그림 1-7 클래스다이어그램

1.3.4. 원칙과 패턴#

개방 폐쇄 원칙(OCP, Open-Closed Priciple)#

  • "개방 폐쇄 원칙"은 클래스나 모듈은 확장에는 열여있어야 하고 변경에는 닫혀있어야 함을 뜻한다.
  • UserDao는 DB연결을 생성하는 기능은 확장할 수 있고 나머지는 사용자 정보를 제어하는 데는 닫혀있다.
  • 인터페이스를 통해 제공하는 확장 포인트는 확장을 위해 활짝 개발되어 있고 인터페이스를 이용하는 클래스는 자신의 변화가 불필요하게 일어나지 않도록 닫혀있다.
객체지향 설계원칙(SOLID)
책 84페이지에 설명있음. 참고

높은 응집도와 낮은 결합도#

  • 개방폐쇄원칙은 "높은 응집도와 낮은 결합도"라는 소프트웨어 개발의 고전적인 원리로도 설명이 가능하다.
  • 응집도가 높다는 것은 하나의 모듈, 클래스가 하나의 책임또는 관심사에만 집중되어 있다는 뜻이다.
  • 응집도가 높다는 것은 변화가 일어나면 해당 모듈에서 변하는 부분이 크다는 것으로도 설명할 수 있다.
  • 낮은 결합도는 하나의 변경이 발생할때 마치 파문이 이는 것처럼 여타 모듈과 객체로 변경에 대한 요구가 전파되지 않는 것을 말한다.

전략패턴(Strategy Pattern)#

1.4. 제어의 역전(IoC)#

1.4.1. 오브젝트 팩토리#

팩토리#

  • 팩토리는 객체의 생성방법을 결정하고 그렇게 만들어진 오브젝트를 돌려주는 역할을 담당한다.
public class UserDaoFactory {
	public UserDao userDao() {
		ConnectionMaker connectionMaker = new DConnectionMaker();
		UserDao dao = new UserDao(connectionMaker);
		return dao;
	}
}

public class UserDaoTest {
	public static void main(String[] args) throws ClassNotFoundException, SQLException {
		UserDao dao = new UserDaoFactory().userDao();
		.....
	}
}

설계도로서의 팩토리#

그림 1-8 클래스다이어그램

1.4.2. 오브젝트 팩토리의 활용#

public class UserDaoFactory {
	public UserDao userDao() {
		UserDao dao = new UserDao(new DConnectionMaker());
		return dao;
	}

	public AccountDao accountDao() {
		AccountDao dao = new AccountDao(new DConnectionMaker());
		return dao;
	}

	public MessageDao messageDao() {
		MessageDao dao = new MessageDao(new DConnectionMaker());
		return dao;
	}
}
  • Dao가 많아질수록 커넥션을 생성하는 new DConnectionMaker() 코드가 중복적으로 늘어난다.
public class UserDaoFactory {
	public UserDao userDao() {
		UserDao dao = new UserDao(connectionMaker());
		return dao;
	}

	public AccountDao accountDao() {
		AccountDao dao = new AccountDao(connectionMaker());
		return dao;
	}

	public MessageDao messageDao() {
		MessageDao dao = new MessageDao(connectionMaker());
		return dao;
	}

	public ConnectionMaker connectionMaker() {
		ConnectionMaker connectionMaker = new DConnectionMaker();
		return connectionMaker;
	}
}

1.4.3. 제어권의 이전을 통한 제어관계 역전#

  • 제어의 역전이란 프로그램의 제어 흐름 구조가 바뀌는 것이다.
  • UserDao를 생각해보면 DB에서 사용자 정보를 제어해야 하니 DB에 연결하는 것도 내가 할께.. 라고 말할수 있다.
  • 제어의 역전을 사용하면 UserDao야.. 넌 사용자 정보만 제어해. DB에 연결하는 방법은 내가 알아서 넣어줄께 라고 할수 있는 것이다.

1.5. 스프링의 IoC#

1.5.1. 오브젝트 팩토리를 이용한 스프링 IoC#

애플리케이션 컨텍스트의 설정정보#

  • 스프링 빈 : 스프링이 제어권을 가지고 직접 만들고 관계를 부여하는 오브젝트
  • 빈 팩토리 : 빈의 생성과 관계설정 같은 제어를 담당하는 IoC오브젝트, 애플리케이션 컨텍스트(application context)라고 부르기도한다.
@Configuration
public class DaoFactory {
	@Bean
	public UserDao userDao() {
		UserDao dao = new UserDao(connectionMaker());
		return dao;
	}

	@Bean
	public ConnectionMaker connectionMaker() {
		ConnectionMaker connectionMaker = new DConnectionMaker();
		return connectionMaker;
	}
}

public class UserDaoTest {
	public static void main(String[] args) throws ClassNotFoundException, SQLException {
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(DaoFactory.class);
		UserDao dao = context.getBean("userDao", UserDao.class);
		.....
	}
}

1.5.2. 애플리케이션 컨텍스트의 동작방식#

그림 1-9 클래스다이어그램
  • DaoFactory에 비해 애플리케이션 컨텍스트 사용시 장점
    • 클라이언트는 구체적인 팩토리 클래스를 알 필요가 없다.
    • 애플리케이션 컨텍스트는 종합 IoC서비스를 제공해준다.
    • 애플리케이션 컨텍스트는 빈을 검색하는 다양한 방법을 제공한다.

1.5.3 스프링 IoC의 용어정리#

  • 빈 팩토리
  • 애플리케이션 컨텍스트
  • 설정정보 / 설정메타정보
  • 컨테이너 또는 IoC컨테이너
  • 스프링 프레임워크

1.6. 싱글톤 레지스트리와 오브젝트 스코프#

  • 애플리케이션 컨텍스트
    • 오브젝트 팩토리와 비슷한 방식으로 동작
    • IoC 컨테이너
    • 싱글톤을 저장하고 관리하는 싱글톤 레지스트리

서버 애플리케이션가 싱글톤#

  • 스프링은 대규모 엔터프라이즈 서버환경에서 사용하기 위해 만들어졌다.
  • 대규모의 요청을 처리하기 위해 생성되는 자바 객체가 GC등을 통해 성능에 좋을리라 없다.
  • 객체 인스턴스를 한개만 띄우는 싱글톤을 사용한다.
디자인 패턴. 싱글톤 패턴
책 106페이지에 설명있음. 참고

싱글톤 패턴의 한계#

public class UserDao {
	private static UserDao INSTANCE;

	..... 

	private UserDao(ConnectionMaker connectionMaker) {
		this.connectionMaker = connectionMaker;
	}

	public static synchronized UserDao getInstance() {
		if( INSTANCE == null ) INSTANCE = new UserDao(???);
		return INSTANCE;
	}
	.....
}
  • private 생성자를 갖고 있기 때문에 상속할수 없다.
  • 싱글톤은 테스트하기가 힘들다.
  • 서버환경에서는 싱글톤이 하나만 만들어지는 것을 보장하지 못한다.
  • 싱글톤의 사용은 전역 상태를 만들 수 있기 때문에 바람직하지 못하다.

싱글톤 레지스트리 #

  • 스프링은 직접 싱글톤 형태의 오브젝트를 만들고 관리하는 기능인 싱글톤 레지스트리를 제공한다.
  • 싱글톤 레지스트리는 스태틱 메소드와 priviate생성자를 사용하는 전통적인 싱글톤 코드 대신에 평범한 자바 클래스를 싱글톤으로 활용해준다.
  • 오브젝트 생성에 관련한 모든 권한은 IoC기능을 ㅔ공하는 애플리케이션 컨텍스트가 가진다.

1.6.2. 싱글톤과 오브젝트의 상태#

  • 싱글톤은 멀티쓰레드 환경이라면 상태정보를 가지지 않은 무상태 방식으로 만들어야 한다.
  • 상태가 없는 방식으로 클래스를 만드려면 파라미터와 메소드 로켤변수, 리턴값등을 이용해서 정보를 다룬다.

1.6.3. 스프링 빈의 스코프#

  • 빈이 성성되고 존재하고 적용되는 범위를 스코프라고 한다.
    • 기본은 싱글톤
    • 프로토타입(prototype)
    • 요청(request)
    • 세션(session)

1.7. 의존관계 주입(DI)#

1.7.1. 제어의 역전(IoC)과 의존관계 주입#

스프링의 IoC는 의존관계 주입이라는 용어로 좀더 명확하게 설명할 수 있다.

1.7.2. 런타임 의존관계 설정#

의존관계#

UserDao의 의존관계#

UserDao의 의존관계 주입#

1.7.3. 의존관계 검색과 주입#

1.7.4. 의존관계 주입의 응용#

기능 구현의 교환#

부가기능 추가#

1.7.5. 메소드를 이용한 의존관계 주입#

  • 수정자 메소드를 이용한 주입
  • 일반 메소드를 이용한 주입

1.8. XML이용한 설정#

1.8.1. XML설정#

자바코드 설정정보 XML설정정보
빈 설정파일 @Configuration <beans>
빈의 이름 @Bean methodName() <bean id="methodName"
빈의 클래스 return new BeanClass() class="a.b.c... BeanClass">
userDao.setConnectionMaker(connectionMaker());

<bean id="userDao" class="springbook.dao.UserDao">
<property name="connectionMaker" ref="connectionMaker" />
</bean>

1.8.2 XML을 이용하는 애플리케이션 컨텍스트#

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
						http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
	<bean id="myConnectionMaker" class="springbook.user.dao.DConnectionMaker">
		<property name="driverClass" value="com.mysql.jdbc.Driver" />
		<property name="url" value="jdbc:mysql://localhost/springbook?characterEncoding=UTF-8" />
		<property name="username" value="spring" />
		<property name="password" value="book" />
	</bean>

	<bean id="userDao" class="springbook.user.dao.UserDao">
		<property name="connectionMaker" ref="myConnectionMaker" />
	</bean>
</beans>


ApplicationContext context = new GenericXmlApplicationContext("applicationContext.xml");
실무에서 웹애플리케이션을 사용할때는 web.xml에 org.springframework.web.context.ContextLoaderListener 를 선언한다. 이는 org.springframework.web.context.WebApplicationContext 를 생성하는 것과 같다.

Add new attachment

Only authorized users are allowed to upload new attachments.
« This page (revision-13) was last changed on 23-Oct-2014 07:42 by DongGukLee