본문 바로가기

카테고리 없음

스프링 핵심 원리 - 기본편(2)

이번에는 스프링의 기능들에 대해 중점적으로 학습할 예정이다.

 

컴포넌트 스캔

@ComponentScan을 붙여줘야 한다

(@Configuration도 컴포넌트 스캔이 되기 때문에 이번 예제에서는 AppConfig를 제외시키는 코드를 작성했다.)

 

@Component 에노테이션이 붙은 클래스를 스캔해서 스프링 빈으로 등록한다.

 

@Component만 붙였을 때 의존관계를 주입할 방법이 사라지는데, 따라서 자동 의존 관계 주입이 필요하다.

@Configuration
@ComponentScan(
        excludeFilters = @ComponentScan.Filter(type =  FilterType.ANNOTATION, classes = Configuration.class)
)
public class AutoAppConfig {
  //지금 아무것도 없음
}

 

Component
public class MemberServiceImpl implements MemberService {

    private final MemberRepository memberRepository;

    @Autowired //자동 의존 관계 주입
    public MemberServiceImpl(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

 

 

 

  1. 스프링이 @Component가 붙은 클래스들을 찾아서 스프링 빈으로 등록한다.
  2. 의존관계 자동 주입을 사용한다 -> 타입을 가지고 탐색하여 주입한다.

 

  • 컴포넌트 스캔의 탐색 위치와 대상
    • basePakages 사용하여 탐색 시작 위치 지정
    • 파라미터로 여러 개 넣어서 시작 위치 여러 개 지정 가능
    • basePakageClasses 로 클래스만 지정 가능
@ComponentScan(
        basePackages = "hello.core.member", //여기서부터 찾게 스캔하도록 한다.
        excludeFilters = @ComponentScan.Filter(type =  FilterType.ANNOTATION, classes = Configuration.class)
        //예제를 위해 Configuration은 제외한다는 코드
)

 

  • 지정하지 않는다 => @ComponentScan 이 작성된 설정 정보 클래스의 패키지가 시작 위치이다.
  • 권장 방법: 프로젝트 루트에 AppConfig 같은 설정 정보 클래스를 주고, @ComponentScan을 붙여서 작성한다!
    스프링 부트를 사용하면 프로젝트 시작 위치에 @springBootApplication을 두는 것이 관례이다.
  • 스캔 기본 대상
    • `@Component` : 컴포넌트 스캔에서 사용
      `@Controller` : 스프링 MVC 컨트롤러에서 사용
      `@Service` : 스프링 비즈니스 로직에서 사용
      `@Repository` : 스프링 데이터 접근 계층에서 사용
      `@Configuration` : 스프링 설정 정보에서 사용
    • 위 에노테이션들로 스프링은 부가 기능을 수행하기도 한다.
      • `@Component` : 컴포넌트 스캔에서 사용
        `@Controller` : 스프링 MVC 컨트롤러로 인식
        `@Service` : 특별한 처리 없음. 개발자들이 비즈니스 계층 로직을 인식할 수 있게함.
        `@Repository` : 스프링 데이터 접근 계층으로 인식. 데이터 계층의 예외를 스프링 예외
        `@Configuration` : 스프링 설정 정보에서 사용. 싱글톤 유지하도록 추가 처리.
  • 컴포넌트 스캔의 필터 옵션
    • includeFilters
      • 컴포넌트 스캔에 추가할 대상을 지정
    • excludeFilters
      • 컴포넌트 스캔에서 제외할 대상을 지정
    • 최근 스프링부트는 컴포넌트 스캔을 기본적으로 제공하기 때문에 기본으로 사용하자!
  • 중복 등록과 충돌
    • 자동 빈 등록 vs 자동 빈 등록
      • 컴포넌트 스캔에 의해 자동으로 스프링 빈이 등록되는데, 그 이름이 같은 경우
      • ConflictingBeanDefinitionException 예외가 터진다. -> 오류를 발생시킨다.
    • 수동 빈 등록 vs 자동 빈 등록
      • 수동 빈이 우선권을 가진다.
      • 수동 빈이 자동 빈을 오버라이딩한다.
      • 이렇게 되면 잡기 어려운 버그가 생길 가능성이 크다. 따라서 최근 스프링부트에서는
        수동 빈과 자동 빈이 충돌하면 오류를 내도록 기본적으로 설정하였다.

 

의존관계 주입

 

생성자 주입 / setter 주입 / 필드 주입 / 일반 메서드 주입

 

  • 생성자 주입
    • 생성자를 호출하는 시점에 딱 한 번만 호출하는 게 보장된다.
    • 불변, 필수 의존관계에 사용한다.
    • 객체를 변경하지 않는 설계에 용이
    • 생성자가 하나 있으면 @Autowired 에노테이션을 생략해도 된다!
  • 수정자 주입
    • set함수를 만들어서 의존관계를 주입한다.
    • 선택, 변경 관계에 있는 의존관계에 사용한다.
@Component
public class OrderServiceImpl implements OrderService {

    private MemberRepository memberRepository;
    private DiscountPolicy discountPolicy;

    @Autowired
    public void setMemberRepository(MemberRepository memberRepository) {
        System.out.println("memberRepository = " + memberRepository);
        this.memberRepository = memberRepository;
    }

    @Autowired
    public void setDiscountPolicy(DiscountPolicy discountPolicy) {
        System.out.println("discountPolicy = " + discountPolicy);
        this.discountPolicy = discountPolicy;
    }

 

  • 자바 빈 프로퍼티
    • 필드의 값을 직접 변경하지 않고, setXxx, getXxx 라는 메서드를 통해서 값을 읽거나 수정하는 규칙
  • 필드 주입
    • 클래스의 필드 부분에 @Autowired를 붙여 사용한다.
    • 되도록 사용하지 않는 것이 좋다!
@Component
public class OrderServiceImpl implements OrderService {

    @Autowired private MemberRepository memberRepository;
    @Autowired private DiscountPolicy discountPolicy;

 

 

  • 일반 메서드 주입
    • 일반 메서드를 통해 주입 받을 수 있어서 한 번에 여러 필드를 주입 받을 수 있다.
private MemberRepository memberRepository;
    private DiscountPolicy discountPolicy;


    public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
        this.memberRepository = memberRepository;
        this.discountPolicy = discountPolicy;
    }

    @Autowired
    public void init(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
        this.memberRepository = memberRepository;
        this.discountPolicy =  discountPolicy;
    }

 

 

결론적으로 자동 의존관계 주입은 스프링 컨테이너가 관리하는 스프링 빈이여야만
@Autowired를 사용할 수 있다.

 

 

옵션 처리

주입할 스프링 빈이 없는 경우에도 동작해야 할 경우가 있다.

@Autowired만 사용하면 required: true 로 되어있어 주입 대상이 없으면 오류가 발생한다.

 

static class TestBean{

        @Autowired(required = false)
        public void setNoBean1(Member noBean1){ //Member는 스프링 빈이 아니다
            System.out.println("noBean1 = " + noBean1);
        }

        @Autowired
        public void setNoBean2(@Nullable Member noBean2){ //스프링이 제공하는 Nullable을 사용해야 함!
            System.out.println("noBean2 = " + noBean2);
        }

        @Autowired
        public void setNoBean3(Optional<Member> noBean3){
            System.out.println("noBean3 = " + noBean3);
        }
    }

 

1번에서 required = false이면 메서드가 호출이 되지 않는다!

 

 

생성자 주입

  • 불변
    • 객체를 생성할 때 한 번만 호출되기 때문에 불변하게 설계 가능하다.
  • 누락
    • 프레임워크 없이 순수 자바 코드를 테스트하는 경우에
      수정자 의존 관계라면 의존관계 주입이 누락될 가능성이 있다.
    • 생성자 주입을 사용하면 컴파일 오류를 통해 누락된 상황을 쉽게 인지할 수 있다.
    • final 키워드를 사용할 수 있다! => 생성자 코드가 누락된 경우, final코드가 있으면 
      오류를 통해 발견할 수 있다.
  • 항상 생성자 주입 방식을 사용하고 수정자 주입 방식을 옵션으로 부여한다.

 

Lombok 라이브러리

@Getter / @Setter 같은 어노테이션을 작성하면 이 라이브러리가 자동으로 get/set 메서드를 만들어준다!

@Getter
@Setter
public class HelloLombok {

    private String name;
    private int age;

    public static void main(String[] args) {
        HelloLombok helloLombok = new HelloLombok();
        helloLombok.setName("asdfs");

        String name = helloLombok.getName();
        System.out.println("name = " + name);
    }
}

 

toString(), wait() 등 종류가 많다.

 

  • 활용
    • @RequiredArgsConstructor
    • 생성자를 자동으로 만들어준다. => 필드에 final 이 있으면 값이 필수적으로 필요하기 때문에
      이를 보고 롬복이 만든다.
@Component
@RequiredArgsConstructor
public class OrderServiceImpl implements OrderService {

    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;

    @Override
    public Order createOrder(Long memberId, String itemName, int itemPrice) {
        Member member = memberRepository.findById(memberId);
        int discountPrice = discountPolicy.discount(member, itemPrice);

        return new Order(memberId, itemName, itemPrice, discountPrice);
    }

코드가 상당히 줄어들었다!

임의의 의존관계가 추가될 때 굉장히 용이하다.

 

 

조회할 빈이 2개 이상일 때 @Autowired

@Autowired는 타입으로 조회하기 때문에

ac.getBean(DiscountPolicy.class); 이런 것과 유사하다.

따라서, 이름만 다르고 완전히 같은 타입의 스프링 빈 2개가 있을 때 해결이 되지 않는다.

 

  • @Autowired에 필드명 매칭
    • 처음엔 타입 매칭을 시도하고, 여러 빈이 조회되면 파라미터 이름으로 빈을 추가한다.
    • 필드 명, 파라미터 명으로 빈 이름을 매칭한다.
  • @Qualifier 사용
    • 추가 구분자
    • 주입 시 추가적인 방법을 제공한다.
    • @Qualifier를 작성하고 옆에 작성된 이름을 찾는다.
    • 그러나 @Qualifier는 @Qualifier를 찾는 용도로만 사용하는 것이 좋다.
  • @Primary
    • 우선순위를 지정하는 방법이다.
    • @Autowired 시에 @Primary가 있으면 우선권을 가진다.
    • 깔끔하게 사용할 수 있어서 자주 사용한다.

우선순위
`@Primary` 는 기본값 처럼 동작하는 것이고, 
`@Qualifier` 는 매우 상세하게 동작한다. 

스프링은 자동보다는 수동이, 넒은 범위의 선택권 보다는 좁은 범위의 선택권이 우선 순위가 높다.
따라서 여기서도 `@Qualifier` 가 우선권이 높다.

 

에노테이션 직접 만들기

package hello.core.annotation;

import org.springframework.beans.factory.annotation.Qualifier;

import java.lang.annotation.*;

@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Qualifier("mainDiscountPolicy")
public @interface MainDiscountPolicy {
}

 

이런 식으로 작성하면 @Qualifier 안에 문자열로 생긴 오류를 잡지 못하다가

에노테이션을 만들면 컴파일 오류를 사용할 수 있다.

 

조회한 빈이 모두 필요할 때

 

static class DiscountService{                                                                       
    private final Map<String, DiscountPolicy> policyMap;                                            
    private final List<DiscountPolicy> policies;                                                    
                                                                                                   
    @Autowired                                                                                      
    public DiscountService(Map<String, DiscountPolicy> policyMap, List<DiscountPolicy> policies) { 
        this.policyMap = policyMap;                                                                
        this.policies = policies;                                                                  
        System.out.println("policyMap = " + policyMap);                                            
        System.out.println("policies = " + policies);                                              
    }

 

생성자 주입을 통해 모든 DiscountPolicy 타입을 주입받는다. 현재 rate와 fix discount policy가 있다!

public int discount(Member member, int price, String discountCode) {
    DiscountPolicy discountPolicy = policyMap.get(discountCode);    
    return discountPolicy.discount(member, price);

 

이런 다형성 메서드를 유지하면서 사용이 가능하다.

 

 int discountPrice = discountService.discount(member, 10000, "fixDiscountPolicy");
 
 
 
 int rateDiscountPrice = discountService.discount(member, 20000, "rateDiscountPolicy");

 

메서드의 discountCode로 rate 또는 fix discountPolicy가 넘어오면 Map에서 해당하는 스프링 빈을 찾아서 실행할 수 있다!

 

  • Map: 스프링 빈의 이름이 키 값이고, 조회한 타입의 모든 스프링 빈을 가진다.
  • List: 모든 스프링 빈의 인스턴스를 가진다.

 

결론

  • 편리한 자동 등록과 자동 의존 관계 주입을 적극 활용
  • 기술 지원 객체는 수동 등록 ex) DataSource,,
  • 다형성을 사용하는 비즈니스 로직은 수동 등록도 고려해보아야 함! => 코드를 한 눈에 보기 쉽다.

 

<빈 생명주기>

데이터베이스 커넥션 풀, 네트워크 소켓처럼 필요한 연결을 미리 해두고, 애플리케이션 종료 시점에 연결을 모두 종료하는 작업을

스프링이 제공한다 == 객체의 초기화와 종료 작업

 

스프링 빈은 객체 생성 -> 의존관계 주입 순으로 실행된다.

의존관계 주입이 다 끝난 후에 사용할 수 있는 데이터가 된다.

그렇다면 의존 관계 주입이 언제 완료되었는지 알 수 있을까?

 

스프링은 의존관계 주입이 완료되면 스프링 빈에게 콜백 메서드를 통해 초기화 시점을 알려주는
다양한 기능을 제공한다.

 

  • 초기화 콜백: 빈 생성되고, 빈의 의존관계 주입이 완료된 후에 호출된다.
  • 소멸전 콜백: 빈이 소멸되기 전에 호출된다. 

객체의 생성과 초기화를 분리한다
생성자는 메모리를 할당해서 객체를 생성한다

초기화는 외부 커넥션을 연결하는 역할을 한다.

 

  • 인터페이스로 초기화, 소멸전 콜백 받는 법
    • InitializingBean, DisposableBean 인터페이스를 가져와서 구현하는 방법으로 사용한다.
    • 인터페이스가 스프링 전용 인터페이스라 요즘에는 거의 사용하지 않는다.
public class NetworkClient implements InitializingBean, DisposableBean {

    private String url;

    public NetworkClient(){
        System.out.println("생성자 호출, url = " + url);
    }

    public void setUrl(String url){
        this.url = url;
    }

    //서비스 시작 시 호출
    public void connect(){
        System.out.println("connect: " + url);
    }

    public void call(String message){
        System.out.println("call: " + url + " message = " + message);
    }

    //서비스 종료 시 호출
    public void disconnect(){
        System.out.println("close: " + url);
    }

    @Override
    public void afterPropertiesSet() throws Exception { //의존관계 주입 후
        System.out.println("NetworkClient.afterPropertiesSet");
        connect();
        call("초기화 연결 메시지");
    }

    @Override
    public void destroy() throws Exception { //소멸 전
        System.out.println("NetworkClient.destroy");
        disconnect();
    }
}

 

 

  • 소멸 메서드 지정
    • 메서드 이름을 자유롭게 할 수 있고, 스프링에 대해 의존하지 않는다.
    • 외부 라이브러리에서도 초기화, 종료 메서드를 적용할 수 있다.
    • @Bean의 `destroyMethod` 는 기본값이 `(inferred)` (추론)으로 등록되어 있다.
    • `close` , `shutdown` 라는 이름의 메서드를 자동으로 호출해준다.
      이름 그대로 종료 메서드를 추론해서 호출해준다.
@Bean(initMethod = "init", destroyMethod = "close")

 

  • 에노테이션 지정
    • @PostConstructor, @PreDestroy  -> 스프링에서도 권장하는 방식
    • jakarta.annotation에 있는 자바 표준이다!
    • 외부 라이브러리에는 적용하지 못한다!
@PostConstruct
    public void init() throws Exception {
        System.out.println("NetworkClient.init");
        connect();
        call("초기화 연결 메시지");
    }

    @PreDestroy
    public void close() throws Exception {
        System.out.println("NetworkClient.close");
        disconnect();
    }

 

 

빈 스코프

스코프는 빈이 존재할 수 있는 범위를 뜻한다.

지금까지 스프링 컨테이너가 종료될 때 까지 유지된다고 학습했는데 그 이유는
스프링 빈은 기본적으로 싱글톤 스코프를 가지기 때문이다.

 

  • 다양한 스코프 지원
    • 싱글톤: 기본 스코프, 시작부터 종료까지 유지되는 가장 넓은 범위의 스코프
    • 프로토타입: 빈의 생성, 의존관계 주입까지만 관여, 더는 관리하지 않는다.
    • 웹 관련
      • request: 웹 요청이 들어오고 나갈 때
      • session: 웹 세션이 생성되고 종료될 때까지 유지
      • application: 웹의 서블릿 컨텍스와 같은 범위로 유지
public class PrototypeTest {

    @Test
    void prototypeBeanFind(){
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(PrototypeBean.class);

        System.out.println("find prototypeBean1");
        PrototypeBean prototypeBean1 = ac.getBean(PrototypeBean.class);
        System.out.println("find prototypeBean2");
        PrototypeBean prototypeBean2 = ac.getBean(PrototypeBean.class);
        System.out.println("prototypeBean1 = " + prototypeBean1);
        System.out.println("prototypeBean2 = " + prototypeBean2);
        Assertions.assertThat(prototypeBean1).isNotSameAs(prototypeBean2);

        ac.close();
    }

    @Scope("prototype")
    static class PrototypeBean{
        @PostConstruct
        public void init(){
            System.out.println("PrototypeBean.init");
        }

        @PreDestroy
        public void destroy(){
            System.out.println("PrototypeBean.destroy");
        }
    }
}

 

프로토타입 스코프는 스프링 컨테이너에서 스프링 빈을 조회할 때 생성되고 초기화 메서드가 실행된다.

종료 메소드는 실행되지 않는다.

따라서 위 예제는 다른 스프링 빈 2개가 생성된다.

 

  • 프로토타입 스코프
    • 스프링 컨테이너에 요청할 때 마다 새로 생성
    • 스프링 컨테이너는 프로토타입 빈의 생성과 의존관계 주입, 초기화까지만 관여한다.
    • 종료 메서드가 호출되지 않는다.
    • 따라서 프로토타입 빈은 이 빈을 조회한 클라이언트가 관리해야 한다. 종료 메서드에 대한 호출도 직접 해야한다.
  • 싱글톤에서 프로토타입 빈을 사용하는 경우
    • 생성 시점에 프로토타입을 주입 받고 그것이 유지된다.
    • 그러나, 프로토타입 스코프의 용도로 봤을 때 이런 경우보다 사용할 때 마다 
      새로 생성해서 사용하는 것을 원할 것이다.
@Test
    void singletonClientUsePrototype(){
        AnnotationConfigApplicationContext ac =
                new AnnotationConfigApplicationContext(ClientBean.class, PrototypeBean.class);

        ClientBean clientBean1 = ac.getBean(ClientBean.class);
        int count1 = clientBean1.logic();
        assertThat(count1).isEqualTo(1);

        ClientBean clientBean2 = ac.getBean(ClientBean.class);
        int count2 = clientBean2.logic();
        assertThat(count2).isEqualTo(2);
    }

    @Scope("singleton")
    static class ClientBean{
        private final PrototypeBean prototypeBean; //생성 시점에 주입

        @Autowired
        public ClientBean(PrototypeBean prototypeBean) {
            this.prototypeBean = prototypeBean;
        }

        public int logic(){
            prototypeBean.addCount();
            return prototypeBean.getCount();
        }
    }

    @Scope("prototype")
    static class PrototypeBean{
        private int count = 0;

        public void addCount(){
            count++;
        }

        public int getCount(){
            return count;
        }

카운트가 1이였다가 2로 증가한다.

프로토타입 스코프가 유지된다는 것을 알 수 있다.

 

  • 웹 스코프
    • request요청이 들어와서 나갈 때 까지 유지되고, 스프링이 관리한다.
    • HTTP request 요청마다 각각 할당되는 웹 스코프이다.
package hello.core.common;

import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.stereotype.Component;

import java.util.UUID;

@Component
@Scope(value = "request")
public class MyLogger {

    private String uuid;
    private String requestURL;

    public void setRequestURL(String requestURL) {
        this.requestURL = requestURL;
    }

    public void log(String message) {
        System.out.println("["+uuid+"]"+ "[" + requestURL+"]" + message);
    }

    @PostConstruct
    public void init(){
        uuid = UUID.randomUUID().toString();
        System.out.println("["+uuid+"] request scope bean create: " + this);
    }

    @PreDestroy
    public void close(){
        System.out.println("["+uuid+"] request scope bean close: " + this);
    }

}

 

웹 스코프로 설정한 MyLogger를 활용하여 컨트롤러를 만들어서 웹 화면을 출력하고자 한다.

package hello.core.web;

import hello.core.common.MyLogger;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@RequiredArgsConstructor
public class LogDemoController {

    private final LogDemoService logDemoService;
    private final MyLogger myLogger;

    @RequestMapping("log-demo")
    @ResponseBody
    public String logDemo(HttpServletRequest request){
        String requestURL = request.getRequestURL().toString();

        System.out.println("myLogger = " + myLogger.getClass());
        myLogger.setRequestURL(requestURL);

        myLogger.log("controller test");
        logDemoService.logic("testId");
        return "OK";
    }
}

 

우선 이렇게 되면 스코프가 활성화되지 않았다는 오류가 발생한다.
그 이유는 웹 스코프는 요청이 들어와야 빈이생성되는데

실제 고객의 아무런 요청이 없기 때문이다. 따라서 Provider로 해결할 수 있다.

@Controller
@RequiredArgsConstructor
public class LogDemoController {
	private final LogDemoService logDemoService;
	private final ObjectProvider<MyLogger> myLoggerProvider;
	
    @RequestMapping("log-demo")
	@ResponseBody
	public String logDemo(HttpServletRequest request) {
		String requestURL = request.getRequestURL().toString();
		MyLogger myLogger = myLoggerProvider.getObject();
        
		myLogger.setRequestURL(requestURL);
		myLogger.log("controller test");
		logDemoService.logic("testId");
		return "OK";
	}
}

 

logic을 실행하는 서비스 계층에도 Provider를 설정해주었다.

ObjectProvider 덕분에 `ObjectProvider.getObject()` 를 호출하는 시점까지
request scope 빈의 생성을 지연할 수 있다.

  • 스코프와 프록시
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyLogger {

    private String uuid;
    private String requestURL;

 

이렇게 proxyMode를 설정해주는 방법이 있다.

적용 대상이 구체 클래스이면 TARGET_CLASS

적용 대상이 인터페이스면 INTERFACES 로 작성한다.

 

이렇게 작성하면 코드를 Provider 사용 이전 상태에서 사용할 수 있다.

아까 작성했던 코드임에도 오류가 나지 않고 동작한다!

package hello.core.web;

import hello.core.common.MyLogger;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@RequiredArgsConstructor
public class LogDemoController {

    private final LogDemoService logDemoService;
    private final MyLogger myLogger;

    @RequestMapping("log-demo")
    @ResponseBody
    public String logDemo(HttpServletRequest request){
        String requestURL = request.getRequestURL().toString();

        System.out.println("myLogger = " + myLogger.getClass());
        myLogger.setRequestURL(requestURL);

        myLogger.log("controller test");
        logDemoService.logic("testId");
        return "OK";
    }
}

 

  • 프록시 모드를 사용하면 CGLIB라는 라이브러리로 내 클래스를 상속 받은 가짜 프록시 객체를 주입한다.
  • 이러한 가짜 프록시 객체는 요청이 오면 내부에서 진짜 빈을 요청하는 위임 로직이 들어있다.
  • 따라서 특정 객체 조회가 필요한 시점까지 지연처리가 가능하다!
  • 이런 특별한 scope는 꼭 필요한 곳에만 사용하자

 

 


 

지금까지 스프링의 핵심 원리와 기술들에 대해 학습해보았다.

워낙 방대해서 모든 것을 알 수 없지만, 우선 배우는 자세로 임하였다.

 

그리고 이를 통해 직접 서버를 구축해보고 잘 모르는 것이 있으면

찾아가며 학습할 예정이다.

 

다음엔 HTTP 통신에 대해 정리하고 넘어갈 예정이다!

파이팅!