토비의 스프링 예제코드!
- 깃허브에서 다운로드 가능
// 1.1.2 코드 - 초난감DAO
public class UserDao {
public void add(User user) throws ClassNotFoundException, SQLException {
////////////DB연결코드////////////
Class.forName("com.mysql.jdbc.Driver");
Connection c = DriverManager.getConnection(
"jdbc:mysql://localhost/springbook", "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 {
////////////DB연결코드////////////
Class.forName("com.mysql.jdbc.Driver");
Connection c = DriverManager.getConnection(
"jdbc:mysql://localhost/springbook", "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;
}
}
미래의 변화를 대비하여 관심사가 같은 것들 끼리는 하나의 객체로, 다른 것들은 가능한 따로 떨어져서 영향을 주지 않도록 하는 프로그래밍의 기초 개념
기존의 코드를 외부의 동작방식(실행의 결과)에는 변화 없이 내부 구조를 변경해서 재구성 하는 작업
- 코드가 간결해져 이해하기 쉬워짐
- 변화에 효율적으로 대응할 수 있음
중복되어있는 코드를 하나의 독립적인 메소드로 작성하는 것
- 이를 리팩토링에서는 메소드 추출(extract method)기법 이라고 한다
// 1.2.2 코드
// 예제 코드에서 중복되어있는 DB연결 코드를 하나의 독립적인 메소드로 작성
public class UserDao {
public void add(User user) throws ClassNotFoundException, SQLException {
////////////DB연결코드//////////// - 중복된 코드
//Class.forName("com.mysql.jdbc.Driver");
// Connection c = DriverManager.getConnection(
//"jdbc:mysql://localhost/springbook", "spring", "book");
////////////////////////////////
Connection c = getConnection(); // 함수만 호출하면 같은 작업 수행
...
}
public User get(String id) throws ClassNotFoundException, SQLException {
////////////DB연결코드//////////// - 중복된 코드
//Class.forName("com.mysql.jdbc.Driver");
// Connection c = DriverManager.getConnection(
//"jdbc:mysql://localhost/springbook", "spring", "book");
////////////////////////////////
Connection c = getConnection(); // 함수만 호출하면 같은 작업 수행
...
}
//중복 코드를 하나의 독립적인 메소드로 분리
private Connection getConnection() throws ClassNotFoundException,
SQLException{
////////////DB연결코드////////////
Class.forName("com.mysql.jdbc.Driver");
Connection c = DriverManager.getConnection(
"jdbc:mysql://localhost/springbook", "spring", "book");
////////////////////////////////
return c;
}
}
코드를 각각 다르게 구현하여 사용해야하는 경우 추상메소드를 만들어 상속받는 클래스가 알아서 각각 구현하도록 함
// 1.2.3 코드
// DB연결 코드를 추상메소드로 만들어 각 제조사에서 구현하도록 함
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;
}
//N 제조사
public class NUserDao extends UserDao{
public Connection getConnection() throws ClassNotFoundException,
SQLException{
//N제조사의 구현코드
}
}
//D 제조사
public class DUserDao extends UserDao{
public Connection getConnection() throws ClassNotFoundException,
SQLException{
//N제조사의 구현코드
}
}
- UserDao를 상속하여 add(), get()은 자유롭게 사용
- getConnection()을 오버라이딩하여 필요에 맞게 구현하여 사용가능
기능의 일부를 추상 메소드나 오버라이딩이 가능한 메소드로 만들고
서브 클래스에서 메소드를 필요에 맞게 구현해서 사용하는 방법
슈퍼클래스(부모)에서 서브클래스에서 구현할 메소드를 호출하여 필요한 타입의 오브젝트를 가져옴
- 구체적인 오브젝트 생성방법을 서브클래스가 정함
- 자바는 다중상속을 허용하지 않음
- 이미 상속을 받은 경우면 사용할 수 없으며, 추후에 상속을 적용하기 힘들다- 상속은 슈퍼클래스의 메소드를 사용할 수 있어 슈퍼클래스가 변화하면 다시 개발해야 할 수도 있다
- 확장된 기능을 다른 클래스에도 적용하려면 계속 상속을 해야한다
- 코드가 매번 중복되어 나타나는 문제가 발생
성격이 다른 관심사를 가진 코드를 하나의 독립적인 클래스로 분리
- 메소드가 아니라서 완전히 독립적인 관심사가 됨
// 1.3.1 코드
public class UserDao {
private SimpleConnectionMaker simpleConnectionMaker;
public UserDao() {
simpleConnectionMaker = new SimpleConnectionMaker();
}
public void add(User user) throws ClassNotFoundException, SQLException {
Connection c = simpleConnectionMaker.makeNewConnection();
...
}
public User get(String id) throws ClassNotFoundException, SQLException {
Connection c = simpleConnectionMaker.makeNewConnection();
...
}
}
public class SimpleConnectionMaker {
public Connection makeNewConnection() throws ClassNotFoundException,
SQLException{
////////////DB연결코드////////////
Class.forName("com.mysql.jdbc.Driver");
Connection c = DriverManager.getConnection(
"jdbc:mysql://localhost/springbook", "spring", "book");
////////////////////////////////
return c;
}
}
- Connection메소드를 SimpleConnectionMaker라는 하나의 클래스로 재생성
- 클래스를 호출하여 사용함으로서 관심을 완전히 분리
- 상속하여 썻을때와 달리 기능을 확장하여 사용하도록 하지 못함
- 변경을 위해서는 관련 있는 코드를 직접 수정해야함
인터페이스를 통해 두 클래스 사이 추상적인 느슨한 연결고리 생성
// 1.3.2 코드
public class UserDao {
private ConnectionMaker connectionMaker;
public UserDao() {
connectionMaker = new DConnectionMaker();
//connectionMaker = new NConnectionMaker();
}
public void add(User user) throws ClassNotFoundException, SQLException {
Connection c = connectionMaker.makeNewConnection();
...
}
public User get(String id) throws ClassNotFoundException, SQLException {
Connection c = connectionMaker.makeNewConnection();
...
}
}
//DB연결 인터페이스
public interface ConnectionMaker {
public Connection makeConnection() throws ClassNotFoundException,
SQLEXception;
}
public class DConnectionMaker implements ConnectionMaker {
public Connection makeConnection() throws ClassNotFoundException,
SQLException{
// D제조사의 구현코드
}
}
public class NConnectionMaker implements ConnectionMaker {
public Connection makeConnection() throws ClassNotFoundException,
SQLException{
// N제조사의 구현코드
}
}
- ConnectionMaker인터페이스 타입 변수를 생성하여 느슨한 연결 유지
- ConnectionMaker인터페이스를 상속하여 각 제조사가 필요에 맞게 구현
사용시 클래스 자체 생성자를 호출하는 코드가 남아있음
public class UserDao {
private ConnectionMaker connectionMaker;
public UserDao() {
connectionMaker = new DConnectionMaker(); //클래스를 직접 호출!!
}
...
}
의존관계를 설정하는 책임을 분리
//UserDao의 생성자 수정
public class UserDao {
...
//public UserDao() {
//connectionMaker = new DConnectionMaker(); //클래스를 직접 호출!!
//}
//수정
public UserDao(ConnectionMaker connectionMaker){
this.connectionMaker = connectionMaker;
}
...
}
//새로운 클래스 UserDaoTest 생성
public class UserDaoTest {
public static void main(String[] args) throws ClassNotFoundException,
SQLException{
// UserDao가 사용할 ConnectionMaker설정
ConnectionMaker connectionMaker = new DConnectionMaker(); //D제조사 의존
//ConnectionMaker connectionMaker = new NConnectionMaker(); //N제조사 의존
UserDao dao = new UserDao(connectionMaker); // 오브젝트 제공하여 의존관계 설정
}
- UserDao가 클래스를 결정하여 생성하던 변수를 매개변수로 받음
- ConnectionMaker를 구현한 클래스를 결정하는 책임을 분리
클래스나 모듈은 확장에는 열려있어야하고 변경에는 닫혀있어야 한다
- 클래스나 모듈이 기능을 확장할때 다른 클래스나 메소드에 영향을 주지않는 것
변경이 일어날 때 해당 기능을 지닌 모듈의 많은 부분이 함께 변하면 응집도가 높다
- 관심사가 비슷한 것들이 한곳에 몰려있다
책임과 관심사가 다른 오브젝트들과는 느슨하게 연결된 형태를 유지하는 것
- 관심사가 다르면 독립적으로 구현하여 변화가 일어날때 영향이 없도록 함
자신의 기능 맥락에서 필요에 따라 변경이 필요한 알고리즘을 인터페이스를 통해 통째로 분리시키고 이를 구현한 클래스를 필요에 따라 바꿔쓸 수 있도록 하는 패턴
프로그램의 제어 흐름 구조가 뒤바뀌는 것
- 오브젝트가 자신이 직접 사용할 오브젝트를 생성하거나 선택하지 않음
- 심지어는 자기자신이 어디서 어떻게 만들어지고 사용되는지 모름
- 제어의 역전의 예
- 템플릿 메소드 패턴의 서브클래스가 작성한 확장한 메소드는 자기자신이 언제 어떻게 사용될지 모름
- 슈퍼클래스가 필요에 의해 호출되서 사용이 되는 형태
- 라이브러리와 달리 프레임워크는 자신이 흐름을 주도하며 필요에 의해 개발자가 만든 코드를 호출해서 사용하는 형태이다
- 프레임워크에 의해 코드가 수동적으로 사용되는 형태
- 반면 라이브러리는 코드가 라이브러리를 호출해서 쓰는 능동적 형태
객체의 생성 방법을 결정하고 만들어진 오브젝트를 돌려주는 오브젝트
- 디자인 패턴의 팩토리메소드와는 다른 것이니 혼동하지 않도록 주의!
public class DaoFactory{
public UserDao userDao{
// UserDao가 사용할 ConnectionMaker설정
ConnectionMaker connectionMaker = new DConnectionMaker(); //D제조사 의존
//ConnectionMaker connectionMaker = new NConnectionMaker(); //N제조사 의존
UserDao dao = new UserDao(connectionMaker); // 오브젝트 제공하여 의존관계 설정
return userDao;
}
}
public class UserDaoTest {
public static void main(String[] args) throws ClassNotFoundException,
SQLException{
UserDao dao = DaoFactory().userDao();//팩토리가 대신 생성
}
}
- 클라이언트(UserDaoTest)가 직접 생성하지 않고 팩토리에 요청하여 받아서 사용
- 클라이언트가 필요에 의해 팩토리를 호출
- UserDao는 자신이 어떤 ConnectionMaker를 받을지 모름
- 심지어는 UserDao 자기자신이 언제 어디서 어떻게 사용되는지 자신은 모름
⇒ 제어의 역전이 잘 적용된 형태
팩토리에 같은 기능이 필요한 다른 클래스들이 들어왔을때 중복을 제거하기 위해 그 기능을 하는 오브젝트를 생성
public class DaoFactory{
public UserDao userDao{
//사용할 ConnectionMaker설정
ConnectionMaker connectionMaker = new DConnectionMaker(); //D제조사 의존
//ConnectionMaker connectionMaker = new NConnectionMaker(); //N제조사 의존
UserDao dao = new UserDao(connectionMaker); // 오브젝트 제공하여 의존관계 설정
return userDao;
}
public AccountDao accountDao() {
//사용할 ConnectionMaker설정
ConnectionMaker connectionMaker = new DConnectionMaker(); //D제조사 의존
//ConnectionMaker connectionMaker = new NConnectionMaker(); //N제조사 의존
return new AccountDao(connectionMaker);
}
}
//다음 코드가 중복
{
//사용할 ConnectionMaker설정
ConnectionMaker connectionMaker = new DConnectionMaker(); //D제조사 의존
//ConnectionMaker connectionMaker = new NConnectionMaker(); //N제조사 의존
}
// 다음과 같이 수정
public class DaoFactory{
public UserDao userDao{
return new UserDao(connectionMaker());
}
public AccountDao accountDao() {
return new AccountDao(connectionMaker());
}
public ConncetionMaker connectionMaker() {
return new DConnectionMaker();
//return new NConnectionMaker();
}
}
스프링이 제어권을 가지고 직접 만들고 관계를 부여하는 오브젝트
- 스프링 빈 : 스프링 컨테이너가 생성, 관계설정, 사용을 제어하는
제어의 역전이 적용된 오브젝트
빈의 생성과 관계설정과 같은 제어를 담당하는 IoC 오브젝트
IoC방식을 따라 만들어진 빈 팩토리(bean factory)
- 빈 팩토리와 동일하지만 IoC엔진의 의미가 좀 더 부각됨
- 오브젝트 팩토리에서 사용했던 원리와 같은 원리를 사용
@Configuration //애플리케이션 컨텍스트 또는 빈 팩토리가 사용할 설정정보라는 표시
public class DaoFactory{
@Bean //오브젝트 생성을 담당하는 IoC용 메소드라는 표시
public UserDao userDao{
return new UserDao(connectionMaker());
}
@Bean
public ConncetionMaker connectionMaker() {
return new DConnectionMaker();
//return new NConnectionMaker();
}
}
public class UserDaoTest {
public static void main(String[] args) throws ClassNotFoundException,
SQLException{
//애플리케이션 컨텍스트 적용
ApplicationContext context =
new AnnotationConfigApplicationContext(DaoFactory.class);
UserDao dao =
context.getBean("userDao", UserDao.class); //빈 이름만 알면 접근가능
}
}
- 팩토리와 동일하게 클라이언트가 필요에 의해 애플리케이션 컨택스트를 호출
- 애플리케이션 컨텍스트는 빈의 이름으로 사용할 오브젝트를 알아서 찾음
- 만들어진 오브젝트를 클라이언트가 받아서 사용
- 클라이언트는 구체적인 팩토리 클래스를 알 필요가 없다
- 애플리케이션 컨텍스트는 종합 IoC서비스를 제공해준다
- 애플리케이션 컨텍스트는 빈을 검색하는 다양한 방법을 제공한다
완전히 같은 오브젝트 '=='연산자로 비교
- 주소값까지 같은 오브젝트
동일한 정보를 가진 오브젝트 'equals()'메소드로 비교
- 주소값은 다르지만 동일한 정보를 지님
여러번에 걸쳐 오브젝트를 요청해도 매번 동일한 오브젝트가 나오는 것
- 하나의 오브젝트만 만들어서 그것을 공유하는 형태
오브젝트를 하나만 만들도록 강제하는 패턴
- 하나만 만들어진 오브젝트는 전역적으로 접근가능
- private 생성자를 가지고 있어 상속불가
- 테스트 하기가 힘듦
- 서버환경에서는 싱글톤이 하나만 만들어지는 것을 보장하지 못함
- 싱글톤의 사용은 전역상태를 만들어 바람직하지 못함
스프링이 제공하는 직접 싱글톤 형태의 오브젝트를 만들고 관리하는 기능
- 싱글톤 패턴과 달리 스프링이 지향하는 객체지향적인 설계에 아무런 제약이 없다!
빈이 생성되고, 존재하고, 적용되는 범위
- 기본 스코프는 싱글톤이다 (경우에 따라 다른 스코프를 가질 수 있음)
IoC라는 용어가 폭넓게 사용되는 용어여서 스프링의 IoC 기능을 명확하게 설명하지 못함
- 의존관계 주입(DI)라는 의도가 명확히 드러나는 이름 사용
다른 클래스나 모듈이 변화하면 그에 영향을 받아 자신도 변화하는 것
- A가 B에게 의존하고 있다 - B가 변화하면 A에 영향을 미친다
- 의존관계엔 방향성이 존재한다 (A가 변화해도 B에는 영향이 없다)
오브젝트 레퍼런스를 외부로부터 제공(주입)받고 이를 통해 여타 오브젝트와 다이나믹하게 의존관계가 만들어지는 것
- 조건
- 클래스 모델이나 코드에는 런타임 시점의 의존관계가 드러나지 않는다
- 이를 위해서 인터페이스에만 의존해야함!- 런타임 시점의 의존관계는 컨테이너나 팩토리 같은 제3의 존재가 결정
- 의존관계는 사용할 오브젝트에 대한 래퍼런스를 외부에서 제공(주입) 해줌으로써 만들어짐
public class DaoFactory{
public UserDao userDao{
return new UserDao(connectionMaker());
}
}
public class UserDaoTest {
public static void main(String[] args) throws ClassNotFoundException,
SQLException{
UserDao dao = DaoFactory().userDao();//팩토리가 대신 생성
}
}
- UserDao는 ConnectionMaker에만 의존하고있음
- DConnectionMaker가 바뀌어도 아무런 영향이 없다
- 인터페이스에만 의존을 하고있으면 결합도가 낮다- DaoFactory가 UserDao와 DConnectionMaker사이의 의존관계를 주입해줌
- UserDao와 DConnectionMaker는 런타임시점에 의존관계가 형성됨
⇒ 의존관계 주입의 조건 만족!
- 사전에 DB를 연결하겠다고 설계는 미리 해놨지만 DB코드의 구체적인 내용은 UserDao가 알 수 없음
- DConnectionMaker는 런타임 시에 의존관계를 맺는 대상 - 의존 오브젝트
- 의존관계 주입 : 클라이언트와 의존 오브젝트를 연결해주는 작업
(클라이언트에 의존 오브젝트를 주입해주는 작업)
의존관계를 맺는 방법이 외부에서의 주입이 아닌 스스로 검색을 이용
- 자신이 스스로 어떤 클래스의 오브젝트를 선택하는건 아님
- 외부에서 만든 오브젝트를 가져올때, 스스로 컨테이너에게 요청하는 방식
public class UserDao {
...
//이전코드
public UserDao(ConnectionMaker connectionMaker){
this.connectionMaker = connectionMaker;
}
//의존관계 검색코드
public UserDao(){ //UserDao가
DaoFactory daofactory = new DaoFactory();
this.connectionMaker = daoFactory.connectionMaker();//직접 DaoFactory에게 요청
}
//의존관계 검색코드 - 애플리케이션 컨택스트 사용
public UserDao() {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(DaoFactory.class);
this.connectionMaker =
context.getBean("connectionMaker", ConnectionMaker.class);
}
...
}
기능자체는 거의 동일함
- 의존관계 주입이 코드가 더욱 간결함
- 의존관계 주입은 성격이 다른 오브젝트에 의존하지 않음
⇒ 대개는 의존관계 주입을 쓰는게 바람직 하다
- 의존관계 검색에서는 검색하는 오브젝트 자신이 스프링의 빈일 필요가 없다
- 의존관계 주입에서는 주입받는 오브젝트도 컨테이너가 만드는 빈 오브젝트여야한다
파라미터로 전달된 값(외부)을 인스턴스 변수(내부)에 저장하는것이 핵심기능
- 외부로부터 받은 오브젝트를 내부의 메소드에서 사용하는 DI방식에 활용 가능
수정자는 한번에 하나의 파라미터만 가지는 제약이 존재
- 여러개의 파라미터를 갖는 일반메소드를 통해 DI용으로 사용
스프링의 어노테이션 == XML설정정보
- 요즈음은 잘 쓰지 않음