의존성 주입(Dependency Injection, DI)
의존성이 있는 클래스의 인스턴스를 직접 생성하지 않고 spring 컨테이너로부터 생성된 Bean을 주입 받는 것
IoC를 실현시키기 위한 세부적인 개념
제어의 역전(Inversion of Control, IoC)
빈 생성, 사용, 삭제 등의 전반적인 제어권을 어플리케이이션이 갖는게 아니라 프래임워크의 컨테이너에게 넘기는 개념
개방 폐쇠 원칙(Open-Close Principle, OCP)
모듈은 확장에는 열려있어야 하지만 변경에는 닫혀있어야 한다는 원칙. 즉 기존 코드를 변경하지 않으면서(Close) 기능을 추가(Open)할 수 있도록 설계가 되어야 한다는 원칙입니다.
이 원칙을 지키기 위해서 주로 객체지향의 추상화와 다형성을 활용합니다.
ex) DB의 save기능을 가진 Oracle, Mysql DB를 모듈을 만들어두고 다른 DB를 추가하는 상황
- save 기능을 추상화하여 DB인터페이스를 만듭니다.
- 다형성 성질을 이용하DB인터페이스를 구현한 클래스 Oracle, Mysql을 만듭니다.
- DB인터페이스를 구현한 MongoDB클래스만 추가하면 됩니다.
=> 기존 코드들은 변경되지 않았고 새로운 기능은 추가되었습니다. OCP 원칙을 지킨 것입니다.
사용하는 이유
결합도를 낮추고 응집도를 높혀서 변경에 유연한 코드를 작성하기 위함.
- 결합도 - 모듈간 의존 정도, 한 모듈 수정을 위해 다른 모듈의 변경을 요구하는 정도
- 응집도 - 연관성이 높은 요소들이 모여있는 정도
기능 변경시 코드 수정을 적게 하기 위해 결합도를 낮추고 응집도를 높혀야 한다.
개방 폐쇄 원칙에 따라서 인터페이스, 클래스를 설계하고 제어의 역전 방식으로 의존관계를 설정하면 결합도를 낮추고 응집도를 높힐 수 있다.
제어의 역전 예시
우선 의존관계를 직접 설정하는 코드입니다.
public class BestStar {
private Star star;
public BestStar()
star = new Star();
// BestStar 생성자에서 Star 클래스와의 의존관계를 애플리케이션 단에서 직접 설정하고 있다.
}
}
그런데 개발자가 직접 의존관계를 설정해주는 코드를 지우고 스프링이 제공하는 @Autowired 어노테이션을 사용하면 프레임워크가 BestStar, Star 오브젝트 의존관계를 설정합니다.
public class BestStar {
@Autowired
private Star star;
}
개방 폐쇄 원칙 예시
DB 종류가 변경되더라도 빈 생성하는 코드 부분 한 줄만 수정하면 된다.
//return new MemoryMemberRepository();
return new JdbcMemberRepository(dataSource);
- SpringConfig 클래스에서 MemberRepository 빈을 생성한다. 이 때, MemoryMemberRepository, JdbcMemberRepository중 선택해서 빈을 생성할 수 있다.
- MemberService 클래스가 MemberRepository 인터페이스를 의존하고 있다.
- MemberRepository 인터페이스를 MemoryMemberRepository, JdbcMemberRepository에서 구현하고 있다.
package hello.hellospring;
import hello.hellospring.repository.JdbcMemberRepository;
import hello.hellospring.repository.MemberRepository;
import hello.hellospring.repository.MemoryMemberRepository;
import hello.hellospring.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
@Configuration
public class SpringConfig {
private DataSource dataSource;
@Autowired
public SpringConfig(DataSource dataSource) {
this.dataSource = dataSource;
}
@Bean
public MemberService memberService() {
return new MemberService(memberRepository());
}
@Bean
public MemberRepository memberRepository() {
//return new MemoryMemberRepository();
return new JdbcMemberRepository(dataSource);
}
}
package hello.hellospring.service;
import hello.hellospring.repository.MemberRepository;
import hello.hellospring.domain.Member;
import java.util.List;
import java.util.Optional;
public class MemberService {
private final MemberRepository memberRepository;
public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
/**
* 회원 가입
*/
public Long join(Member member) {
validateDuplicateMember(member); // 중복 회원 검증
memberRepository.save(member);
return member.getId();
}
private void validateDuplicateMember(Member member) {
memberRepository.findByName(member.getName())
.ifPresent(m-> {
throw new IllegalStateException("이미 존재하는 회원입니다.");
});
}
/**
* 전체 회원 조회
*/
public List<Member> findMembers() {
return memberRepository.findAll();
}
public Optional<Member> findOne(Long memberId) {
return memberRepository.findById(memberId);
}
}
package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import java.util.List;
import java.util.Optional;
public interface MemberRepository {
Member save(Member member);
Optional<Member> findById(Long id);
Optional<Member> findByName(String name);
List<Member> findAll();
}
package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import java.util.*;
public class MemoryMemberRepository implements MemberRepository {
// 동시성 문제로 HashMap 대신 ConcurrentHashMap을 사용해야 함. 나중에 적용해볼까?
public static Map<Long, Member> store = new HashMap<>();
// 동시성 문제로 HashMap 대신 AtomicLong 을 사용해야 함. 나중에 적용해볼까?
public static Long sequence = 0L;
@Override
public Member save(Member member) {
member.setId(++sequence);
store.put(member.getId(), member);
return member;
}
@Override
public Optional<Member> findById(Long id) {
return Optional.ofNullable(store.get(id));
}
@Override
public Optional<Member> findByName(String name) {
/*
// 스트림
long count = IntStream.of(1, 3, 5, 7, 9).count();
long sum = LongStream.of(1, 3, 5, 7, 9).sum();
OptionalInt min = IntStream.of(1, 3, 5, 7, 9).min();
OptionalInt max = IntStream.of(1, 3, 5, 7, 9).max();
DoubleStream.of(1.1, 2.2, 3.3, 4.4, 5.5)
.average()
.ifPresent(System.out::println);
OptionalInt reduced =
IntStream.range(1, 4) // [1, 2, 3]
.reduce((a, b) -> {
return Integer.sum(a, b);
});
List<String> list = List.of("Peter", "Thomas", "Edvard", "Gerhard");
// print using lambda
list.forEach(item -> System.out.println(item));
// print using :: (method reference operator)
list.forEach(System.out::println);*/
return store.values().stream()
.filter(member-> member.getName().equals(name))
.findAny();
}
@Override
public List<Member> findAll() {
return new ArrayList<>(store.values());
}
public void clearStore() {
store.clear();
}
}
package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import org.springframework.jdbc.datasource.DataSourceUtils;
import javax.sql.DataSource;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
public class JdbcMemberRepository implements MemberRepository {
private final DataSource dataSource;
public JdbcMemberRepository(DataSource dataSource) {
this.dataSource = dataSource;
}
@Override
public Member save(Member member) {
String sql = "insert into member(name) values(?)";
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
conn = getConnection();
pstmt = conn.prepareStatement(sql,
Statement.RETURN_GENERATED_KEYS);
pstmt.setString(1, member.getName());
pstmt.executeUpdate();
rs = pstmt.getGeneratedKeys();
if (rs.next()) {
member.setId(rs.getLong(1));
} else {
throw new SQLException("id 조회 실패");
}
return member;
} catch (Exception e) {
throw new IllegalStateException(e);
} finally {
close(conn, pstmt, rs);
}
}
@Override
public Optional<Member> findById(Long id) {
String sql = "select * from member where id = ?";
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
conn = getConnection();
pstmt = conn.prepareStatement(sql);
pstmt.setLong(1, id);
rs = pstmt.executeQuery();
if(rs.next()) {
Member member = new Member();
member.setId(rs.getLong("id"));
member.setName(rs.getString("name"));
return Optional.of(member);
} else {
return Optional.empty();
}
} catch (Exception e) {
throw new IllegalStateException(e);
} finally {
close(conn, pstmt, rs);
}
}
@Override
public List<Member> findAll() {
String sql = "select * from member";
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
conn = getConnection();
pstmt = conn.prepareStatement(sql);
rs = pstmt.executeQuery();
List<Member> members = new ArrayList<>();
while(rs.next()) {
Member member = new Member();
member.setId(rs.getLong("id"));
member.setName(rs.getString("name"));
members.add(member);
}
return members;
} catch (Exception e) {
throw new IllegalStateException(e);
} finally {
close(conn, pstmt, rs);
}
}
@Override
public Optional<Member> findByName(String name) {
String sql = "select * from member where name = ?";
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
conn = getConnection();
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, name);
rs = pstmt.executeQuery();
if(rs.next()) {
Member member = new Member();
member.setId(rs.getLong("id"));
member.setName(rs.getString("name"));
return Optional.of(member);
}
return Optional.empty();
} catch (Exception e) {
throw new IllegalStateException(e);
} finally {
close(conn, pstmt, rs);
}
}
private Connection getConnection() {
return DataSourceUtils.getConnection(dataSource);
}
private void close(Connection conn, PreparedStatement pstmt, ResultSet rs)
{
try {
if (rs != null) {
rs.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if (pstmt != null) {
pstmt.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if (conn != null) {
close(conn);
}
} catch (SQLException e) {
e.printStackTrace();
}
}
private void close(Connection conn) throws SQLException {
DataSourceUtils.releaseConnection(conn, dataSource);
}
}
참고
https://beststar-1.tistory.com/33
https://creamilk88.tistory.com/137
https://devfunny.tistory.com/8
https://gardentree.tistory.com/36
https://yoongrammer.tistory.com/97
'programming study > B-Spring' 카테고리의 다른 글
spring-AOP (2) | 2022.09.20 |
---|---|
spring-@Resource, @Autowired, @Inject, 주입 방법(필드,생성자,setter) (0) | 2022.09.16 |
spring-Dependency (0) | 2022.09.16 |
spring - 빈 등록 (@Component, @Bean, @Configuration) (0) | 2022.09.16 |
spring-싱글톤 (0) | 2022.09.14 |