Spring 컨테이너 생성 과정

// @Configuration 어노테이션으로 등록된 AppConfig 클래스
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
  • BeanFactory를 최상위 인터페이스로 가지고 있다
  • ApplicationContext를 스프링 컨테이너라 한다.
    • BeanFactory의 기능을 모두 상속받아서 가지고 있다.
    • 그밖에도 여러가지 부가 기능(인터페이스 구현)들을 제공한다
      • 환경변수(EnvironmentCapable 인터페이스)
      • 메세지소스를 활용한 국제화 기능(MessageSource 인터페이스)
      • 애플리케이션 이벤트 (ApplicatinoEventPublisher 인터페이스)
      • 편리한 리소스 조회 (ResourceLoader 인터페이스)
  • ApplicationContext는 인터페이스이다.
  • Spring Bean을 가지고 필요한 순간마다 의존성을 주입하는 일을 한다.
    • 직접적으로 Bean으로 등록
    • FatoryBean을 사용해 등록 @Bean
  • 스프링 컨테이너는 XML 기반으로도 만들 수 있고 에노테이션 기반의 자바 설정 클래스로 스프링 컨테이너를 만들 수 도 있다.
  • 스프링 컨테이너는 오직 BeanDefinition 인터페이스(추상화)만을 의존한다
    • BeanDefinition은 @Configuration의 내용 혹은 xml 파일의 내용을 보고 내용을 담는다.

<학습 강의>

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/dashboard

제어의 역전

  • 구현 객체가 프로그램의 제어 흐름을 스스로 조종했던 방식에서
  • 프로그램의 제어 흐름을 직접 제어하는 것이 아니라 외부에서 관리하는 방식으로 바꾼 것을 제어의 역전(IoC)이라 한다.

프레임워크 vs 라이브러리

  • 프레임워크가 내가 작성한 코드를 제어하고, 대신 실행하면 그것은 프레임워크가 맞다 ex: JUnit
  • 반면에 내가 작성한 코드가 직접 제어의 흐름을 담당한다면 그것은 프레임워크가 아니라 라이브러리다.

의존관계 주입DI

  • 실행 시점(런타임)에 외부에서 실제 구현 객체를 생성하고 클라이언트에 전달해서 클라이언트와 서버의 실제 의존관계가 연결 되는 것을 의존관계 주입 이라 한다.
  • 의존관계는 “정적인 클래스 의존 관계와, 실행 시점에 실행되는 동적인 객체(인스턴스)의존 관계” 둘을 분리해서 생각해야 한다.
  • 정적인 클래스 의존관계: import 코드만 보면 어떤 의존 관계인지 한 번에 알 수 있는 경우(정적인 의존관계는 에플리케이션을 실행하지 않아도 분석 할 수가 있다)

IoC 컨테이너, DI 컨테이너

  • 컨테이너는 객체를 생성하고 관리하면서 의존관계를 연결해 주는 역활을 하는 클래스를 말한다.
  • Spring 에서는 @Configuration 어노테이션과 @Bean 어노테이션을 통해 Spring 컨테이너에 등록하고 ApplicationContext Class를 통해 사용
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
applicationContext.getBean("memberService",MemberService.class);
  • 이때 Spring 컨테이너에 등록된 객체를 Spring Bean이라고 한다.

'Development > Spring' 카테고리의 다른 글

웹 애플리케이션과 싱글톤  (0) 2023.03.15
스프링 컨테이너와 스프링 빈  (0) 2023.03.14
스프링  (0) 2023.03.12
AOP (Aspect Oriented Programming) 관점 지향 프로그래밍  (0) 2023.03.11
JPA  (0) 2023.03.11

스프링

  • 스프링 DI 컨테이너 기술
  • 스프링 프레임워크
  • 스프링 부트, 스프링 프레임워크 등을 모두 포함한 스프링 생태계
  • 자바 언어 기반의 프레임워크
  • 객체 지향 언어가 가진 강력한 특징을 살려내는 프레임워크
  • 좋은 객체 지향 애플리케이션을 개발할 수 있게 도와주는 프레임워크

스프링 프레임워크

  • 핵심 기술: 스프링 DI 컨테이너, AOP, 이벤트, 기타
  • 웹 기술: 스프링 MVC, 스프링 WebFlux
  • 데이터 접근 기술, 트랜잭션, JDBC, ORM 지원, XML 지원
  • 기술 통합: 캐시, 이메일, 원격접근, 스케줄링

스프링 부트

  • 스프링을 편리하게 사용할 수 있도록 지원, 최근에는 기본으로 사용
  • 단독으로 실행할 수 있는 스프링 애플리케이션을 쉽게 생성
  • Tomcat 같은 웹 서버를 내장해서 별도의 웹 서버를 설치하지 않아도 됨
  • 손쉬운빌드 구성을 위한 starter 종속성 제공
  • 스프링과 3rd parth(외부) 라이브러리 자동 구성
  • 메트릭, 상태 확인, 외부 구성 같은 프로덕션 준비 기능 제공
  • 관례에 의한 간결한 설정

<학습 강의>

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/dashboard

'Development > Spring' 카테고리의 다른 글

스프링 컨테이너와 스프링 빈  (0) 2023.03.14
IoC, DI, 그리고 컨테이너  (0) 2023.03.14
AOP (Aspect Oriented Programming) 관점 지향 프로그래밍  (0) 2023.03.11
JPA  (0) 2023.03.11
Spring Bean과 의존성 주입  (0) 2023.03.10

AOP 예시 코드

package aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class TimeTraceAop {
    @Around("execution(* hello.hellospring..*(..))")
    public Object execute(ProceedingJoinPoint joinPoint) throws  Throwable{
        long start = System.currentTimeMillis();
        System.out.println("START: "+joinPoint.toString());
        try{
            return joinPoint.proceed();
        }finally {
            long finish = System.currentTimeMillis();
            long timesMs = finish - start;
            System.out.println("END: "+joinPoint.toString()+" "+timesMs+"ms");
        }

    }
}
  • 각각의 메소드에 따로따로 구현하지 않더라도 모든 메소드들의 시간 측정 가능

AOP가 필요한 상황

  • 모든 메소드의 호출 시간을 측정하고 싶다면?
    • 공통 관심사항(시간 측정)과 핵심 관심(기능)사항이 섞여서 유지보수가 힘들어짐
  • 공통 관심사항(cross-cutting concern)과 핵심 관심사항(core concern)이 합쳐진 상황을 분리시키고 싶을 때

AOP 사용되는 이유

  • 공통 관심사항(cross-cutting concern)과 핵심 관심사항(core concern)을 분리하고 원하는 순간에 적용하기 위해서
  • 공통 관심사항을 따로 만들고 Bean에 등록하는 것 으로 중복된 코드들을 방지 할 수 있음

실제 동작 과정

  • 스프링 컨테이너에서 검사할 항목에 가짜 Spring bean을 생성(프록시) 후 호출

'Development > Spring' 카테고리의 다른 글

IoC, DI, 그리고 컨테이너  (0) 2023.03.14
스프링  (0) 2023.03.12
JPA  (0) 2023.03.11
Spring Bean과 의존성 주입  (0) 2023.03.10
컴퓨터 부팅 시 TomCat서버 자동 시작 설정  (0) 2023.03.09

JPA (자바 퍼시스턴스 API)

  • 기존의 반복 코드는 물론이고 기본적인 SQL도 JPA가 직접 만들어서 실행해준다.
// Jdbc 를 사용한 직접 sql문을 기입하여 하는 코드
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);
        }
    }
}
// JPA를 활용하여 간소화한 버전
public class JpaMemberRepository implements MemberRepository {
    private final EntityManager em;

    // String Boot 가 자동으로 생성한 것에 의존성만 주입해주면 됨
    public JpaMemberRepository(EntityManager em) {
        this.em = em;
    }

    @Override
    public Member save(Member member) {
        em.persist(member);
        return member;
    }
}
  • SQL과 데이터 중심의 설계에서 객체 중심의 설계로 패러다임을 전환을 할 수 있다.
  • JPA를 사용하면 개발 생상성을 크게 높힐 수가 있다.
  • 객체와 ORM 기술
  • 표준 인터페이스가 제공이됨
    • 구현기술 중 하나가 HIBERNATE
  • 아이덴티티 전략: DB에 값을 넣으면 DB가 알아서 id를 생성해주는 것 @GeneratedValue(strategy = GenerationType.IDENTITY)
  • @Commit 해당 내용이 반영되게끔 하는 어노테이션

스프링 데이터 JPA

  • CRUD 기능도 스프링 데이터 JPA가 모두 제공해줌
  • 조금이라도 단순하고 반복적이던 개발 코드들을 확연하게 줄여주는 프레임워크
  • JPA를 편리하게 사용하도록 도와주는 기술
  • 단순 인터페이스만으로도 간단한 기능을 완성시키는 것이 가능

스프링 Bean이 무엇이지?

  • 스프링 IoC 컨테이너가 관리하는 자바 객체
    • 객체를 생성하고 관리하고 책임지고 의존성을 관리해주는 컨테이너
  • 가급적 스프링 빈에 모든 걸 등록해서 사용하는게 좋다

Spring 개발 패턴

  • 콘트롤러(외부요청 받기)
  • 서비스(비지니스 로직)
  • 레포지터리(데이터 저장)

스프링 빈을 등록하는 2가지 방법

  • 컴포넌트 스캔과 자동 의존관계 설정
  • 자바 코드로 직접 스프링 빈 등록하기

컴포넌트 스캔과 자동 의존관계 설정

  • @Component 에노테이션이 있으면 스프링 빈으로 자동 등록된다.
  • @Controller 컨트롤러가 스프링 빈으로 자동 등록된 이유도 컴포넌트 스캔 때문이다.
  • @Component를 포함하는 다음 애노테이션도 스프링 빈으로 자동 등록된다.
    • @Controller
    • @Service
    • @Repository
    • @Autowired
      • 스프링 빈으로 등록된 클래스 대상으로만 실행됨
  • 스프링 빈에 등록될때 싱글톤 패턴으로 등록하고 공유하도록 되어진다.

자바 코드로 직접 스프링 빈 등록하기

  • @Configuration
  • @Bean
  • 상황에 맞추어 유연하게 바꾸는 것이 가능

DI 의존성 주입의 3가지 방법

  • 생성자 주입
package hello.hellospring.controller;

import hello.hellospring.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class MemberController {

    private final MemberService memberService;

    @Autowired
    public MemberController(MemberService memberService){
        this.memberService = memberService;
    }
}
  • 필드 주입
package hello.hellospring.controller;

import hello.hellospring.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class MemberController {

    @Autowired private final MemberService memberService;

}
  • Setter 주입
package hello.hellospring.controller;

import hello.hellospring.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class MemberController {

    private MemberService memberService;

    @Autowired
    public void setMemberService(MemberService memberService) {
        this.memberService = memberService;
    }
} 

톰켓이 설치된 경로에서 CMD를 키고

.\service.bat install 입력

그러면 작업관리자 서비스에 TomCat이 등록이 됨  

사진에서는 안보이지만 해당 TomCat의 시작유형 을 자동으로 변경 끝 

<Main 디렉토리>

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{
    private static Map<Long,Member> store = new HashMap<>();
    private 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) {
        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();
    }
}

 

<Test 디렉토리>

package hello.hellospring.repository;

import hello.hellospring.domain.Member;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;

import java.util.List;

import static org.assertj.core.api.Assertions.*;

class MemoryMemberRepositoryTest {

    MemoryMemberRepository repository = new MemoryMemberRepository();
    @AfterEach // 모든 검사가 끝날 때마다 실행되도록 하는 어노테이션
    public void afterEach(){
        // 각 검사가 끝날때 내용을 초기화 하지 않으면 예전 저장된 정보가 남아 문제가 생김
        repository.clearStore();
    }

    @Test
    public void save(){
        Member member = new Member();
        member.setName("spring");

        repository.save(member);

        Member result = repository.findById(member.getId()).get();
        //System.out.println("result = "+(result == member));
        //Assertions.assertEquals(member,null);
        assertThat(member).isEqualTo(result);
    }

    @Test
    public void findByName(){
        Member member1 = new Member();
        member1.setName("spring1");
        repository.save(member1);

        Member member2 = new Member();
        member2.setName("spring2");
        repository.save(member2);

        Member result = repository.findByName("spring1").get();
        assertThat(result).isEqualTo(member1);
    }

    @Test
    public void findAll(){
        Member member1 = new Member();
        member1.setName("spring1");
        repository.save(member1);

        Member member2 = new Member();
        member2.setName("spring2");
        repository.save(member2);

        List<Member> result = repository.findAll();

        assertThat(result.size()).isEqualTo(2);
    }
}

+ TTD: 미리 검증 가능한 테스트 틀을 만들어두고 하는 테스트 주도 개발 

윈도우 단축키: Ctrl+Shift+t (테스트 껍대기 자동 생성)

+ 테스트 폴더는 빌드되지 않으므로 직관적으로 한글을 써도 됨

+ 각 항목이 given, when, then의 총 3단계로 나뉘어지며 테스트됨

 

+ Recent posts