다음은 토비의 스프링 3.4 컨텍스트와 DI ~ 3.5장 3템플릿과 콜백 을 공부하고 정리하였습니다.
DI에 대해 다시 복습하자 !
1. 그림으로 표현해본다면?
Pencil 객체와 Store 객체가 있다.
이 때 의존성은 다음과 같이 문장으로 표현된다.
A가 B를 사용한다.
A가 B에 의존성이 있다.
A-store / B-pencil
DI 가 아닌 코드를 먼저 보자.
public class Store {
private Pencil pencil;
public Store() {
this.pencil = new Pencil();
}
}
Store 객체 안에서 직접 객체를 생성하고 초기화한다.
DI 인 코드
public class Store {
private final Pencil pencil;
public Store(Pencil pencil) {
this.pencil = pencil;
}
}
외부에서 Pencil 객체를 주입받는다. (즉, 이미 밖에서 생성된 애다!)
여기에서 우리는 IOC의 개념을 옅볼 수 있는데, 어떠한 객체를 사용할지에 대한 책임은 프레임워크(책에서는 BeanFactory로 설명된다)에게 넘어갔고, 자신은 수동적으로 주입받는 객체만 사용된다. 요즘은 스프링에서 BeanFactory 역할을 다 해준다.
2. DI는 다형성을 가진 interface와 함께 쓰이는 경우가 많다.
구현 클래스를 직접 의존하는 것보다 중간에 추상화 계층을 만들어 Di해주는 것이 확장성 up, 결합도 down에 도움이 된다.
public class Pencil implements Product {..}
JdbcContext 의 분리
- 왜 분리?? jdbcContextwithStatementStrategy() 는 다른 DAO(DB와 소통하기 위한 객체. connection 맺고 ~ 끊고)에서도 사용 가능하다. 이걸 밖으로 독립시켜서 다른 Dao도 사용할 수 있도록 하자! → 명칭은 JdbcContext로 하겠다.
- 이 때 문제?? 그렇다면 DataSource(DB 서버와의 연결을 해주는 기능을 함. DB connection pooling)가 필요한 것은 UserDao가 아니라 JdbcContext(Connection 열기와 닫기, Statement 준비와 닫기, Statement 실행, Exception 처리와 반환) 가 돼버린다.
- 어떻게 해결할 수 있는지? JdbcContext가 DataSource를 사용하고 있다. = JdbcContext는 DataSource에 의존하고 있다. → DataSource 타입 빈을 JdbcContext가 DI 받을 수 있게 해주어야 한다.
package springboot.user.dao;
public class JdbcContext {
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
public void workwithStatementStrategy(StatementStrategy stmt) throws SQLException {
Connection c = null;
PreparedStatement ps = null;
try {
c = this.dataSource.getConnection();
ps = stmt.makepreparedStatement();
ps.executeUpdate();
} catch (SQLException e) {
throw e;
} finally {
if (ps != null) {try {ps.close(); } catch (SQLExcpetion e {} }
if (c != null) {try {c.close(); } catch (SQLExcpetion e {} }
}
}
}
public class UserDao {
private JdbcContext jdbcContext ;
public void setJdbcContext(JdbcContext jdbcContext) {
this.jdbcContext = jdbcContext;
}
public void add(final User user) throws SQLException {
this.jdbcContext.workWithStatementStrategy (
new StatementStrategy () { ... }
);
}
}
JdbcContext를 Bean으로 만들어 DI
→ 파악해보면 UserDao에 JdbcContext를, JdbcContext에는 DataSource를 주입하였다.
UserDao는 JDBC 작업 흐름이 담긴 jdbcContextWithStatementStrategy()를 JdbcContext로 분리하고 DI 받도록 구현하였다 !
여기에서 본래 DI 개념에 대해 다시 좀 더 고민하고 가자. 본래 DI는 인터페이스를 많이 함께 사용한다. User > UserImpl이 그와 같다. 인터페이스를 통해 의존성을 주입하면 클라이언트 코드와 의존성을 분리할 수 있다는 것이 큰 장점이다.
그런데 여기에서는 jdbcContext를 왜 impl로 만들어 한 단계 더 감싸지 않을까? 그러면 더 클래스를 자유롭게 수정할 수 있을텐데? →
- IoC 개념이 탑재되어서 상관없다
- 뒤의 dataSource와의 의존성도 있기 때문에 빈으로 등록되는 것이 좋다. (화살표의 방향에 주의하자) JdbcContext는 다른 빈(dataSource)을 DI 받기 위해서라도 스프링 빈으로 등록되어야 한다.
- 이 셋은 긴밀하고 강한 응집도를 가지고 있다. 어차피 강하니까 그냥 흐뜨러뜨리지 말고 냅둬도 된다.
코드를 이용하는 수동 DI
- 이번에는 가운데의 JdbcContext를 빈으로 등록하지 말고 UserDao 내부에서 직접 DI를 적용하자!
장점 ?
- JdbcContext를 빈으로 등록하지 않아도 됨. 따라서 3단 DI 의 직접적인 클래스간 관계 구조를 숨길 수 있다.
- 인터페이스를 두지 않아도 될만큼 긴밀한 관계를 표현할 수 있으면서도 DI를 망치지 않는다.
단점 ?
- 싱글톤 포기해야 함. 하지만 DB 연결하는 갯수만큼(즉 DAO 갯수만큼)만 만들어지기 때문에 아무리 많아도 수백개 정도이다.
- JdbcContext가 빈이 아니라서 다른 누군가가 JdbcContext를 생성/초기화 해줘야 함.
- 다른 빈(dataSource)을 DI받기 위해 위해서는 빈이어야 하는데 ㅠ → JdbcContext가 dataSource를 Dynamic하게 주입해야 한다ㅠㅠㅠ하나하나 기찮다 → 그럼 주입을 userDao가 해줘 (수동 DI 작업)
템플릿과 콜백 패턴
복잡하지만 바뀌지 않는 일정한 패턴을 갖는 작업 흐름이 존재하고 그 중 일부분만 자주 바꿔서 사용해야 하는 구조에 적합하다.
context를 템플릿, 익명 내부 클래스로 만들어진 오브젝트를 콜백이라고 한다.
class MyClass {
void myMethod(PrintB printB) {
a();
printB.b();
c();
}
void a() {
System.out.println("A");
}
void c() {
System.out.println("C");
}
}
interface PrintB {
void b();
}
public class Main {
public static void main(String[] args) {
MyClass myClass = new MyClass();
myClass.myMethod(()-> System.out.println("B1"));
myClass.myMethod(()-> System.out.println("B2"));
}
}
DI가 숨겨 있다!
위에서 볼 수 있듯이 클라이언트가 템플릿 메소드를 호출하면서 콜백 오브젝트를 전달하는 것은 메소드 레벨에서 일어나는 DI라고 할 수 있다.
JdbcContext 코드에 적용된 템플릿/콜백 패턴
workWithStatementStrategy()에서 확인 가능
저렇게 내부 클래스 쓰는 건 정신 없다 최소화할 수 있는 방법??
by 분리
query를 parameter로 받아서 … 메소드로 만든다.
그러면 executeSql() 메소드는 UserDao 뿐만 아니라 여러 Dao에서도 쓸 수 있을 것 같다. 그러면 DAO가 공유할 수 있는 템플릿 클래스 안(JdbcContext)으로 옮기자 !
템플릿/콜백의 응용 - 핵심은 중복 제거와 분리
- Calculate
- try/catch/finally
추가로 자바 5에서 추가된 Generics(제네릭스, 자바 언어 + 타입 파라미터)를 사용하면 다양한 오브젝트 타입을 지원하는 인터페이스나 메소드를 정의할 수 있다.자료 구조 때 배우는 template 느낌 ..?이다. 한 번 훑어만 보자 !
참고
https://mangkyu.tistory.com/150
https://siyoon210.tistory.com/131
'BackEnd > JAVA\SPRING' 카테고리의 다른 글
[SPRING] AOP (0) | 2023.10.16 |
---|---|
[SPRING] enum 타입, 역할과 책임의 분리 (0) | 2023.10.02 |
[SPRING] DI와 XML을 이용한 설정 (0) | 2023.09.05 |
[SPRING] IOC (0) | 2023.09.03 |
[SPRING] DAO의 분리와 확장 (5) | 2023.09.03 |