Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[7주차](임주민, 오형석, 이승철) #7

Open
sunwootest opened this issue Aug 8, 2023 · 3 comments
Open

[7주차](임주민, 오형석, 이승철) #7

sunwootest opened this issue Aug 8, 2023 · 3 comments
Assignees
Labels

Comments

@sunwootest
Copy link
Collaborator

sunwootest commented Aug 8, 2023

  • 7주차
    • 6.5 ~ 6.6장
    • 6.7 ~ 6.9장
    • 7.1 ~ 7.4장
@sunwootest sunwootest changed the title [7주차](임주민, 양재승, 박준영)) [7주차](임주민, 양재승, 박준영) Aug 8, 2023
@kuk6933 kuk6933 changed the title [7주차](임주민, 양재승, 박준영) [7주차](임주민, 오형석, 이승철) Aug 9, 2023
@kuk6933
Copy link

kuk6933 commented Aug 14, 2023

토비의 스프링 6.5~ 6.6

스프링 AOP

6.5.1 자동 프록시 생성

  • ProxyFactoryBean을 통해 대부분의 문제는 해결됨
  • 하지만 아직 ProxyFactoryBean의 설정을 직접 추가해줘야함

빈 후처리기를 이용한 자동 프록시 생성

  • 스프링은 컨테이너로서 제공하는 기능 중 핵심적인 부분 외에는 대부분 확장할 수 있도록 확장 포인트를 제공
  • 우리가 볼것은 BeanPostProcessor 인터페이스를 구현해서 만드는 빈 후처리기
  • 스프링은 빈 후처리기가 빈으로 등록되어 있으면 빈 오브젝트가 생성될 때마다 빈 후처리기에 보내서 작업을 요청
  • 빈 오브젝트의 프로퍼티 강제 수정 가능
  • 초기화 가능
  • 빈 오브젝트 자체를 바꿔치기 가능
  • 스프링이 생성하는 빈 오브젝트의 일부를 프록시로 포장하고 프록시를 빈으로 대신 등록할 수 있게됨.
    • 이것이 자동 프록시 생성 빈 후처리기
스크린샷 2023-08-14 오전 9 07 12
  • 이를 통해 번거로운 ProxyFactoryBean 설정 문제를 말끔히 해결 가능

6.5.3 포인트컷 표현식을 이용한 포인트컷

  • 포인트컷 표현식을 지원하는 포인트컷을 적용하려면 AspectJExpressionPointCut 클래스를 사용
  • AspectJ 포인트컷 표현식은 포인트컷 지시자를 이용해 작성

스크린샷 2023-08-13 오후 5.28.54.png

  • 접근 제한자
  • 리턴 값의 타입
  • 패지키와 타입 이름을 포함한 클래스의 타입 패턴
  • 메소드 이름 패턴
  • 메소드 파라미터의 타입 패턴
  • 예외 이름에 대한 타입 패턴

6.5.4 AOP란 무엇인가?

지금까지 UserService에 트랜잭션을 적용해온 과정

  • 트랜잭션 서비스 추상화
  • 프록시와 데코레이터 패턴
  • 다이내믹 프록시와 프록시 팩토리 빈
  • 자동 프록시 생성 방법과 포인트 컷
  • 부가기능의 모듈화

AOP: Aspect oriented Programming

Aspect?

  • 핵심 기능을 담고 있지는 않지만 핵심 기능에 부가되어 의미를 갖는 특별한 모듈
    • 부가 기능을 정의한 코드인 어드바이스
    • 어드바이스를 어디에 적용할지 결정하는 포인트컷
  • 애플리케이션의 핵심적인 기능에서 부가적인 기능을 분리해 애스팩트를 만들어 설계하고 개발하는 방법을 AOP라고 함.
  • AOP는 OOP와는 다른 패러다임이라고 느낄 수도 있지만 AOP는 OOP를 돕는 보조 역할

6.5.5 AOP 적용기술

  • AOP의 핵심은 프록시를 이용했다는 것
  • 프록시로 만들어서 DI로 연결된 빈 사이에 적용해 타깃의 메소드 호출 과정에 참여해서 부가기능을 제공하도록 만들어줄 수 있었음

바이트코드 생성과 조작을 통한 AOP

  • 스프링 같은 컨테이너가 사용되지 않는 환경에서도 손쉽게 AOP의 적용이 가능
  • 프록시 방식보다 훨씬 강력하고 유연한 AOP가 가능
    • AspectJ가 이러한 기능을 제공하지만 별도의 환경이 필요하고 일반적인 AOP로도 충분해서 필요시 사용하면 됨

6.5.6 AOP의 용어

  • 타깃
    • 부가기능을 부여할 대상
  • 어드바이스
    • 부가기능을 담은 모듈
  • 조인 포인트
    • 어드바이스가 적용될 수 있는 위치
    • 스프링의 프록시 AOP에서 조인 포인트는 메소드의 실행 단계 뿐
    • 타깃 오브젝트가 구현한 인터페이스의 모든 메소드는 조인 포인트가 될 수 있음
  • 포인트컷
    • 어드바이스를 적용할 조인 포인트를 선별하는 작업 또는 그 기능을 정의한 모듈
  • 프록시
    • 클라이언트와 타깃 사이에 투명하게 존재하면서 부가기능을 제공하는 오브젝트
  • 어드바이저
    • 포인트컷과 어드바이스를 하나씩 갖고있는 오브젝트
  • 애스펙트
    • AOP의 기본 모듈
    • 한개 또는 그이상의 포인트컷과 어드바이스의 조합으로 만들어짐

6.5.7 AOP 네임 스페이스

스프링의 프록시 방식 AOP를 적용하려면 최소 네가지 빈을 등록해야함

  • 자동 프록시 생성기
  • 어드바이스
  • 포인트컷
  • 어드바이저

6.6 트랜잭션 속성

6.6.1 트랜잭션 정의

트랜잭션?

  • 더 이상 쪼갤 수 없는 최소 단위의 작업

DefaultTransactionDefinition 인터페이스는 트랜잭션 동작 방식에 영향을 줄 수 있는 네 가지 속성을 정의

트랜잭션 전파

  • 트랜잭션 경계에서 이미 진행중인 트랜잭션이 있을때 또는 없을 때 어떻게 동작할 것인가를 결정하는 방식
  • PROPAGATION_REQUIRED
    • 진행중인 트랜잭션이 없으면 새로 시작, 있으면 참여
  • PROPAGATION_REQUIRES_NEW
    • 항상 새로운 트랜잭션 시작
  • PROPAGATION_NOT_SUPPORETED
    • 트랜잭션 없이 동작하도록

격리 수준

트랜잭션 격리수준(isolation level)이란 동시에 여러 트랜잭션이 처리될 때, 트랜잭션끼리 얼마나 서로 고립되어 있는지를 나타내는 것. 간단하게 말해 특정 트랜잭션이 다른 트랜잭션에 변경한 데이터를 볼 수 있도록 허용할지 말지를 결정하는 것

  • READ UNCOMMITTED
  • READ COMMITTED
  • REPEATABLE READ
  • SERIALIZABLE

제한시간

  • 트랜잭션을 수행하는 제한 시간 설정 가능

읽기전용

  • ReadOnly로 설정해두면 트랜잭션 내에서 데이터를 조작하는 시도를 막을 수 있고 성능 향상도 가능함.

메소드 이름 패턴을 이용한 트랜잭션 속성 지정

PROPAGATION_NAME, ISOLATION_NAME, readOnly, timeout_NNNN, -Exception1, +Exception2
  • 전파 방식
  • 격리 수준
  • 읽기전용
  • 제한시간
  • 체크 예외 중 롤백 대상으로 추가할 것
  • 런타임 예외지만 롤백 시키지 않을 예외들

6.6.3 포인트컷과 트랜잭션 속성의 적용 전략

  • 트랜잭션 포인트컷 표현식은 타입 패턴이나 빈 이름을 이용
  • 공통된 메소드 이름 규칙을 통해 최소한의 트랜잭션 어드바이스와 속성을 정의
  • 프록시 방식 AOP는 같은 타깃 오브젝트 내의 메소드를 호출할 때는 적용되지 않음 — 주의 사항

6.6.4 트랜잭션 속성 적용

트랜잭션 경계설정의 일원화

  • 일반적으로 특정 계층의 경계를 트랜잭션 경계와 일치시키는 것이 바람직함
  • 비즈니스 로직을 담고 있는 서비스 계층 오브젝트의 메소드가 트랜잭션 경계를 부여하기에 가장 적절한 대상

@tmdcheol
Copy link

@transactional 사용법

  • 선언적
  • 타겟 오브젝트 위에 @transactional를 붙히기만 하면 끝
  • @transactional 애노테이션에 다양한 항목들을 지정해서 사용 가능하며, 매서드 클래스 인터페이스에 적용가능하다.

우선순위

  1. 구체 클래스 메소드
  2. 구체 클래스
  3. 인터페이스 메소드
  4. 인터페이스

활용방법

  • 우선순위를 이용하여, 구체 클래스에 디폴트 값을 적용하고, 메서드에 구체적인 세부사항을 명시할 수 있다!

테스트 코드에서의 @transactional 사용법

  • main 코드에서와는 다르게 롤백된다.
  • 항상 롤백이 되어 값이 보이지 않아, 실제로 확인하고 싶다면 @Rollback(false)를 사용하자
  • 메소드 레벨에서는 @Rollback, 클래스 레벨에서는 @TransactionConfiguration 을 사용한다.

@transactional 을 활용하면 얼마나 편리해지는 것일까?

예시 -> 고객 a가 고객 b에게 5000원을 송금하는 상황

  1. a 잔고에서 5000원 감소
  2. b 잔고에서 5000원 감소

두 가지 작업을 하나의 transaction으로 묶고싶은 상황이다.
서버(클라이언트)는 db 서버에 접근하기 위해 커넥션이 필요하다. 커넥션을 획득하고, db 서버에 세션이라는 것을 만드는데, 이 세션이 실제로 SQL을 실행한다고 생각하면 된다.

a 잔고에서 5000원을 감소시킬 동안 에러가 발생한다면 commit을 하지 않고(데이터 실제 반영을 하지 않고), 롤백을 하며, b 잔고에 5000원을 증가시키는 작업 또한 수행하면 안된다.

이 작업은 Service 단에서 수행 될 것이며, 원래라면 서비스 코드에 DataSource가 필요할 것이다.
Service는 순수한 비지니스 로직만 있는 것이 좋은 코드이다. 책임분리원칙에 따라야 한다.

예시코드

@Slf4j
@RequiredArgsConstructor
public class MemberServiceV2 {
    private final DataSource dataSource;
    private final MemberRepositoryV2 memberRepository;

    public void accountTransfer(String fromId, String toId, int money) throws SQLException {
        Connection con = dataSource.getConnection();
        try {
            con.setAutoCommit(false); //트랜잭션 시작 
            bizLogic(con, fromId, toId, money); //비즈니스 로직
            con.commit(); //성공시 커밋
        } catch (Exception e) {
            con.rollback(); //실패시 롤백
            throw new IllegalStateException(e);
        } finally {
            release(con);
        }
    }

    private void bizLogic(Connection con, String fromId, String toId, int
            money) throws SQLException {
        Member fromMember = memberRepository.findById(con, fromId);
        Member toMember = memberRepository.findById(con, toId);
        memberRepository.update(con, fromId, fromMember.getMoney() - money);
        validation(toMember);
        memberRepository.update(con, toId, toMember.getMoney() + money);
    }

    private void validation(Member toMember) {
        if (toMember.getMemberId().equals("ex")) {
            throw new IllegalStateException("이체중 예외 발생");
        }
    }

    private void release(Connection con) {
        if (con != null) {
            try {
                con.setAutoCommit(true); //커넥션 풀 고려
                con.close();
            } catch (Exception e) {
                log.info("error", e);
            }
        }
    }
}
public class MemberRepositoryV2 {
    private final DataSource dataSource;

    public MemberRepositoryV2(DataSource dataSource) {
        this.dataSource = dataSource;
    }


    public Member findById(Connection con, String memberId) throws SQLException {
        String sql = "select * from member where member_id = ?";
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            pstmt = con.prepareStatement(sql);
            pstmt.setString(1, memberId);
            rs = pstmt.executeQuery();
            if (rs.next()) {
                Member member = new Member();
                member.setMemberId(rs.getString("member_id"));
                member.setMoney(rs.getInt("money"));
                return member;
            } else {
                throw new NoSuchElementException("member not found memberId=" + memberId);
            }
        } catch (SQLException e) {
            log.error("db error", e);
            throw e;
        } finally {
            JdbcUtils.closeResultSet(rs);
            JdbcUtils.closeStatement(pstmt);
        }
    }


    public void update(Connection con, String memberId, int money) throws SQLException {
        String sql = "update member set money=? where member_id=?";
        PreparedStatement pstmt = null;
        try {
            pstmt = con.prepareStatement(sql);
            pstmt.setInt(1, money);
            pstmt.setString(2, memberId);
            pstmt.executeUpdate();
        } catch (SQLException e) {
            log.error("db error", e);
            throw e;
        } finally {
//connection은 여기서 닫지 않는다.
            JdbcUtils.closeStatement(pstmt);
        }
    }


    private void close(Connection con, Statement stmt, ResultSet rs) {
        JdbcUtils.closeResultSet(rs);
        JdbcUtils.closeStatement(stmt);
        JdbcUtils.closeConnection(con);
    }

    private Connection getConnection() throws SQLException {
        Connection con = dataSource.getConnection();
        log.info("get connection={} class={}", con, con.getClass());
        return con;
    }
}

문제점 3가지

  • JDBC 구현 기술이 서비스 계층에 누수되는 문제 -> 트랜잭션을 적용하기 위해 JDBC 구현 기술이 서비스 계층에 누수되었다.
  • 트랜잭션 동기화 문제 -> 같은 트랜잭션을 유지하기 위해 커넥션을 파라미터로 넘겨야 한다.
  • 예외 누수 -> 스프링이 제공하는 추상화된 런타임 예외로 전환하면 된다.

해결법 1 -> TransactionManager

DataSourceUtils 로 커넥션을 가져온다!

image
  1. 서비스 계층에서 transactionManager.getTransaction() 을 호출해서 트랜잭션을 시작한다.
  2. 트랜잭션을 시작하려면 먼저 데이터베이스 커넥션이 필요하다. 트랜잭션 매니저는 내부에서 데이터소스를
    사용해서 커넥션을 생성한다.
  3. 커넥션을 수동 커밋 모드로 변경해서 실제 데이터베이스 트랜잭션을 시작한다.
  4. 커넥션을 트랜잭션 동기화 매니저에 보관한다.
  5. 트랜잭션 동기화 매니저는 쓰레드 로컬에 커넥션을 보관한다. 따라서 멀티 쓰레드 환경에 안전하게 커넥션
    을 보관할 수 있다.
  6. 서비스는 비즈니스 로직을 실행하면서 리포지토리의 메서드들을 호출한다. 이때 커넥션을 파라미터로 전달 하지 않는다.
  7. 리포지토리 메서드들은 트랜잭션이 시작된 커넥션이 필요하다. 리포지토리는 DataSourceUtils.getConnection() 을 사용해서 트랜잭션 동기화 매니저에 보관된 커넥션을 꺼내서 사 용한다. 이 과정을 통해서 자연스럽게 같은 커넥션을 사용하고, 트랜잭션도 유지된다.
  8. 획득한 커넥션을 사용해서 SQL을 데이터베이스에 전달해서 실행한다.
  9. 비즈니스 로직이 끝나고 트랜잭션을 종료한다. 트랜잭션은 커밋하거나 롤백하면 종료된다.
  10. 트랜잭션을 종료하려면 동기화된 커넥션이 필요하다. 트랜잭션 동기화 매니저를 통해 동기화된 커넥션을 획득한다.
  11. 획득한 커넥션을 통해 데이터베이스에 트랜잭션을 커밋하거나 롤백한다.
  12. 전체 리소스를 정리한다.

해결법 2 -> TransactionTemplate.

반복적인 트랜잭션을 위해서 사용되는 반복되는 커밋, 롤백 코드를 제거하자.

    public void accountTransfer(String fromId, String toId, int money) throws SQLException {
        txTemplate.executeWithoutResult((status) -> {
            try {
                bizLogic(fromId, toId, money); // 비지니스 로직
            } catch (SQLException e) {
                throw new IllegalStateException(e);
            }
        });

    }

transactionTemplate이 하는 역할

비즈니스 로직이 정상 수행되면 커밋한다.
언체크 예외가 발생하면 롤백한다. 그 외의 경우 커밋한다.

** 그러나 결국에는 서비스에 트랜잭션을 처리하는 기술로직이 포함된 것이다**

스프링 AOP를 통해 프록시를 도입하면 문제를 깔끔하게 해결할 수 있다.
@transaction을 이용하자!

image image
    @Transactional
    public void accountTransfer(String fromId, String toId, int money) throws SQLException {
        bizLogic(fromId, toId, money);
    }
image

** 그 길었던 코드가 @transaction 어노테이션 코드 하나로 정리된다. **

@jumining
Copy link

jumining commented Aug 14, 2023

7장 스프링 핵심 기술의 응용

목표
: 스프링의 3개 기술인 IoC/DI, 서비스 추상화, AOP를 애플리케이션 개발에 활용해서 새로운 기능을 만들어보고 이를 통해 스프링의 개발철학과 추구하는 가치, 스프링 사용자에게 요구되는 게 무엇인지 파악


SQL과 DAO의 분리

데이터 액세스 로직은 바뀌지 않더라도 DB 테이블, 필드 이름, SQL 문장은 바뀔 가능성 0
SQL 변경 시 -> SQL 담고 있는 DAO 코드도 수정

👉 SQL(DB 테이블과 필드 정보)을 DAO코드와 다른 파일이나 위치에 두고 관리해보는 작업 시도

- XML 설정 이용한 분리

SQL을 스프링의 XML 설정파일로 빼내는 것
설정파일에 프로퍼티 값으로 정의해서 DAO에 주입 가능

// 메소드를 위한 SQL 필드
public class UserDaoJdbc implements UserDao {
	private String sqlAdd;
    
    public void setSqlAdd(String SqlAdd) {
    	this.sqlAdd = sqlAdd;
    }
}

// 주입받은 SQL 사용
public void add(User user) {
	this.jdbcTemplate.update(
    	this.sqlAdd, ... );
}

XML 설정의 UserDao 빈에 sqlAdd 프로퍼티 추가 후 SQL 넣어줌

한계 👉 매번 새로운 SQL이 필요할 때마다 프로퍼티를 추가하고 DI를 위한 변수와 수정자 메소드도 만들어줘야 함

- SQL 맵 프로퍼티 방식

SQL을 하나의 컬렉션으로 담아두는 방법
맵을 이용해서 키 값을 이용해 SQL 문장 가져오기
프로퍼티는 맵 하나만 만들면 되서 코드 간결
SQL 추가로 필요시 설정 파일의 맵 정보(entry)만 변경하면 됨

this.sqlMap.get("add")

정리
: 스프링의 설정파일 안에 SQL을 두고 이를 DI해서 DAO가 사용하게 하는 방식으로 SQL을 코드에서 분리 가능


SQL 제공 서비스

DAO가 사용할 SQL을 제공해주는 기능을 독립시킬 필요 존재 -> 독립적인 SQL 제공 서비스

- SQL 서비스 인터페이스

클라이언트인 DAO를 SQL 서비스의 구현에서 독립적으로 만들도록 인터페이스 사용, DI로 구현 클래스 오브젝트 주입해주기

DAO가 사용할 SQL 서비스의 기능
: SQL에 대한 키 값을 전달하면 그에 해당하는 SQL을 돌려주는 것

this.sqlService.get("userAdd")



인터페이스의 분리와 자기참조 빈

sqlService 인퍼페이스의 구현 방법

- XML 파일 매핑

스프링의 XML 설정파일에서 <bean> 태그 안에 SQL 정보 넣어놓는 것은 좋은 방법이 X
SQL을 저장해두는 전용 포맷(XML)을 가진 독립적인 파일 이용하는 것이 GOOD

  • XML 문서 : 검색용 키와 SQL 문장
  • SQL 서비스 구현 클래스 : XML 파일에서 SQL 읽어뒀다가 DAO에게 제공해줌

- JAXB(Java Architecture for XML Binding)

XML에 담긴 정보를 파일에서 읽어오는 방법 중 하나
XML 문서를 변환하는 방법
XML 정보를 그대로 담고 있는 오브젝트 트리 구조로 만들어주기 때문에 XML 정보를 오브젝트처럼 다룰 수 있어 편리

XML 문서의 구조를 정의한 스키마를 이용해서 매핑할 오브젝트의 클래스까지 자동으로 만들어주는 컴파일러도 제공, 컴파일러를 통해 자동생성된 오브젝트에는 매핑정보가 애노테이션으로 담겨있음

JAXB API는 애노테이션에 담긴 정보를 이용해서 XML과 매핑된 오브젝트 트리 사이의 자동변환 작업을 수행해줘서 XML 문서의 내용을 자바 오브젝트로 변환 가능

JAXB 용어
언마샬링 : XML 문서를 읽어서 자바의 오브젝트로 변환하는 것
마샬링 : 바인딩 오브젝트를 XML 문서로 변환하는 것




XML 파일 이용하는 SQL 서비스

JAXB를 이용해 XML 문서를 변환 후 SqlService에 적용할 차례

- XML SQL 서비스 구현

DAO가 SQL을 요청할 때마다 매번 XML 파일 읽어서 SQL 찾는 건 비효율적

XML을 한 번만 읽고 읽은 내용은 어딘가에 저장 후 요청 올 때마다 사용

👉 생성자에서 JAXB를 이용해 XML로 된 SQL 문서를 읽어와 변환된 Sql오브젝트들을 맵으로 옮겨서 저장해뒀다가 DAO의 요청에 따라 SQL을 찾아서 전달하는 방식 시도

- 개선

생성자에서 예외가 발생할 수 있는 복잡한 초기화 작업을 다루는 것은 좋지 X 상속도 불편하며 보안에도 문제 생길 가능성 존재

👉 별도의 초기화 메소드 사용 후 오브젝트 만드는 시점에서 초기화 메소드 한 번 호출해주기

빈의 초기화 작업

XmlSqlService 오브젝트는 빈이므로 제어권이 스프링에게 있어 생성과 초기화는 스프링이 담당

스프링은 빈 오브젝트 생성하고 DI 작업을 수행해서 프로퍼티 모두 주입 후 미리 지정한 초기화 메소드 호출해주는 기능 가지고 있음

<context:annotation-config \>태그에 의해 등록되는 빈 후처리기는 몇 빈 설정에 사용되는 애노테이션 제공

@PostConstruct : 빈 오브젝트의 초기화 메소드를 지정하는 데 사용
초기화 작업을 수행할 메소드에 부여해주면 스프링이 XmlSqlService 클래스로 등록된 빈의 오브젝트를 생성하고 DI 작업을 마친 뒤에 @PostConstruct가 붙은 메소드를 자동으로 실행해줌

인터페이스 분리

XMl 대신 다른 포맷의 파일에서도 SQL을 읽어오게 하는 법

- 책임에 따른 인터페이스 정의

책임 1) SQL 정보를 외부의 리소스(XML, 엑셀 파일, DB)로부터 읽어오는 것

책임 2) 읽어온 SQL을 보관해두고 있다가 필요할 때 제공

SqlService의 구현 클래스가 변경 가능한 책임을 가진 두가지 타입의 오브젝트를 사용하도록 만든다
인터페이스 사용, DI를 통해 의존 오브젝트 제공받도록 하기




- 다중 인터페이스 구현과 간접 참조

XmlSqlService 클래스 하나가 SqlService, SqlReader, SqlRegistry라는 세 개의 인터페이스를 구현해도 상관 X

책임에 따라 분리되지 않았던 XmlSqlService 클래스를 일단 세부화된 책임을 정의한 인터페이스를 구현하도록 만드는 작업 시도

SqlService의 메소드에서
SQL을 읽을 때는 SqlReader 인터페이스를 통해,
SQL을 읽을 때는 SqlReader 인터페이스를 통해,
SQL을 찾을 때는 SqlRegistry 인터페이스를 통해
간접적으로 접근하게 함

- 자기참조 빈 설정

이제 빈 설정을 통해 실제 DI가 일어나도록 하기
클래스가 하나라서 빈 하나만 등록이 아니라 마치 세 개의 빈이 등록된 것처럼 SqlService 빈이 SqlRegistry와 SqlReader를 주입받도록 만들기

스프링은 프로퍼티의 ref 항목에 자기 자신 넣는 거 허용 -> sqlService를 구현한 메소드와 초기화 메소드는 외부에서 DI된 오브젝트라고 생각하고 결국 자신의 메소드에 접근

자기참조 빈 만드는 방식은 책임과 관심사가 복잡하게 얽혀 있어서 확장 힘들고 변경에 취약한 구조의 클래스를 유연한 구조로 만들려고 할 때 처음 시도해볼 수 있는 방법
코드를 책임 단위로 구분 가능
확장구조 만들어두기 가능

- 디폴트 의존관계를 갖는 빈 만들기

특정 의존 오브젝트가 대부분의 환경에서 거의 디폴트라고 해도 좋을 만큼 기본적으로 사용될 가능성이 있는 경우 사용

디폴트 의존관계 : 외부에서 DI 받지 않는 경우 기본적으로 자동 적용되는 의존관계

DI 설정이 없을 경우 디폴트로 적용하고 싶은 의존 오브젝트를 생성자에서 넣어줌
설정 있으면 무시됨

setSqlReader(new JaxbXmlSqlReader());
setSqlRegistry(new HashMapSqlRegistry());
// 생성자에서 디폴트 의존 오브젝트를 직접 만들어서 스스로 DI 해줌

<이전>

<이후>

단점
: 설정을 통해 다른 구현 오브젝트를 사용하게 해도 생성자에서 일단 디폴트 의존 오브젝트를 다 만들어버림
(사용되지 않는 오브젝트가 만들어짐)




서비스 추상화 적용

OXM 서비스 추상화

- OXM(Object-XML Mapping)

XML과 자바 오브젝트를 매핑해서 상호 변환해주는 기술

서비스 추상화
: 로우레벨의 구체적인 기술과 API에 종속되지 않고 추상화된 레이어와 API를 제공해서 구현 기술에 대해 독립적인 코드를 작성할 수 있게 해줌

스프링은 OXM에 대해서도 서비스 추상화 기능을 제공

스프링이 제공하는 OXM 추상화 서비스 인터페이스

  1. Marshaller : 자바 오브젝트를 XML로 변환
  2. Unmarshaller : XML을 자바 오브젝트로 변환

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants