Servlet(서블릿)

서블릿은 주로 웹 애플리케이션 서버에서 실행되며, HTTP 요청을 처리하고 동적 웹 페이지를 생성하는 데 사용하는 자바 기술입니다.

요약하자면 클라이언트의 요청을 처리하고 동적 페이지를 만드는 데 도움을 주는 기술이다.

만약 웹 애플리케이션 서버를 직접 만든다면 어떻게 구현할까?

어떤 데이터를 저장하기 위한 API를 하나 만든다고 가정해 보자.
해당 웹 애플리케이션 서버는 HTTP 요청을 받고 처리하기 위해서는 다음 단계를 거칠 것이다.

  1. 서버 TCP/IP 연결 대기, 소켓 연결
  2. HTTP 요청 메시지 파싱
  3. HTTP 메서드(GET, POST, PUT, DELETE 등) 확인, URL 확인
  4. Content-type 확인
  5. POST인 경우 HTTP MessageBody 파싱
  6. 비즈니스 로직 실행 (데이터베이스 조회, 저장, 업데이트 등)
  7. HTTP 응답(Response) 메시지 생성
    - HTTP START 라인 작성
    - Header 생성
    - 메시지 바디 입력
  8. TCP/IP에 응답 전달 후, 소켓 종료

딱 봐도 많은 단계를 거쳐서 구현해야 함을 알 수 있다.
서블릿을 사용하는 WAS(웹 애플리케이션 서버)를 사용하면 단계를 훨씬 줄일 수 있다.

서블릿을 사용했을 때의 HTTP 요청 처리 단계를  알아보자

  1. WAS는 HTTP요청이 오면 Request, Response 객체를 생성하여 서블릿 객체를 호출한다.
  2. Request 객체에서 필요한 정보를 편하게 꺼내서 비즈니스 로직을 실행한다.
  3. Response 객체에 HTTP 응답 정보를 입력한다.
  4. WAS는 Response객체의 내용을 바탕으로 HTTP 응답 정보를 생성한다.

서블릿을 활용하면 HTTP 스펙을 편리하게 활용하여 많은 부분에서 이점이 있다.

서블릿만 만들어서는 자동으로 해주는 것이 아니라 이를 관리해 줄 수 있는 게 필요한 데 이를 서블릿 컨테이너라고 부른다.

Servlet Container(서블릿 컨테이너)

대표적인 서블릿 컨테이너로는 톰캣이 있는 데 이처럼 서블릿을 지원하는 WAS를 서블릿 컨테이너라고 한다.

서블릿 컨테이너서블릿 객체를 생성, 초기화, 호출, 종료하는 생명주기를 관리한다.

 

서블릿 객체는 싱글톤으로 생성된다. 왜냐하면 클라이언트가 요청할 때마다 객체를 생성한다면 비효율적이기 때문이다.

따라서 최초 로딩 시점에 서블릿 객체를 미리 생성하여 이를 재활용한다.

모든 클라이언트의 요청은 동일한 서블릿 객체 인스턴스에 접근하며, 싱글톤이기 때문에 공유 변수 사용은 주의해야 한다.

JSP 또한 서블릿으로 변환되어서 사용되며, 동시 요청을 위한 멀티 스레드 처리를 지원한다.

 

Spring Boot & Servlet

앞에서 톰캣이 대표적인 서블릿 컨테이너라고 하였는데 스프링 부트는 톰캣 서버를 내장하고 있어서톰캣 서버 설치 없이

편리하게 서블릿 코드를 실행할 수 있다.

스프링 부트는 서블릿을 직접 등록하여 사용할 수 있도록 @ServletComponentScan을 지원한다. 

@ServletComponentScan 
@SpringBootApplication
public class ServletApplication {
	public static void main(String[] args) {
		SpringApplication.run(ServletApplication.class, args);
	}

}

다음과 같이 추가하면 서블릿을 등록할 수 있다. 서블릿 클래스를 작성해 보자.

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet(name = "helloServlet",urlPatterns = "/hello")
public class HelloServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("HelloServlet.service");
        System.out.println("request = " + request);
        System.out.println("response = " + response);

        String username = request.getParameter("username");
        System.out.println("username = " + username);

        response.setContentType("text/plain");
        response.setCharacterEncoding("utf-8");
        response.getWriter().write("Hello " + username);
    }
}

@WebServlet 어노테이션에서 서블릿의 이름과 URL을 매핑한다.

해당 클래스는 javax.servlet.http.HttpServlet을 상속받는다.
주요 핵심 내용은 HttpServletRequest, HttpServletResponse, 오버라이드한 service()이다.

 

서블릿은 HTTP 요청을 편하게 사용하도록 HTTP 요청 메시지를 대신 파싱해주며 파싱 한 결과를 HttpServletRequest 객체로 제공한다.

HttpServletRequest 객체는 임시 저장소 기능 및 세션 관리 기능도 부가적으로 제공한다.

 

HttpServletResponse는 HTTP 응답 메시지를 생성하고, 응답 코드 지정, 헤더 생성 및 바디를 생성한다.

또한 Content-type, 쿠키, Redirect 등의 편의 기능도 제공한다.

 

service() 메서드를 확인하기 위해 HttpServlet을 한 번 살펴보자.

protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        String method = req.getMethod();

        if (method.equals(METHOD_GET)) {
            long lastModified = getLastModified(req);
            if (lastModified == -1) {
                // servlet doesn't support if-modified-since, no reason
                // to go through further expensive logic
                doGet(req, resp);
            } else {
                long ifModifiedSince;
                try {
                    ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
                } catch (IllegalArgumentException iae) {
                    // Invalid date header - proceed as if none was set
                    ifModifiedSince = -1;
                }
                if (ifModifiedSince < (lastModified / 1000 * 1000)) {
                    // If the servlet mod time is later, call doGet()
                    // Round down to the nearest second for a proper compare
                    // A ifModifiedSince of -1 will always be less
                    maybeSetLastModified(resp, lastModified);
                    doGet(req, resp);
                } else {
                    resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
                }
            }

        } else if (method.equals(METHOD_HEAD)) {
            long lastModified = getLastModified(req);
            maybeSetLastModified(resp, lastModified);
            doHead(req, resp);

        } else if (method.equals(METHOD_POST)) {
            doPost(req, resp);

        } else if (method.equals(METHOD_PUT)) {
            doPut(req, resp);

        } else if (method.equals(METHOD_DELETE)) {
            doDelete(req, resp);

        } else if (method.equals(METHOD_OPTIONS)) {
            doOptions(req, resp);

        } else if (method.equals(METHOD_TRACE)) {
            doTrace(req, resp);

        } else {
            //
            // Note that this means NO servlet supports whatever
            // method was requested, anywhere on this server.
            //

            String errMsg = lStrings.getString("http.method_not_implemented");
            Object[] errArgs = new Object[1];
            errArgs[0] = method;
            errMsg = MessageFormat.format(errMsg, errArgs);

            resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
        }
    }

뭔가 복잡해 보이지만 핵심은 요청(HttpServletRequest)으로부터 메서드 이름을 확인하고, 해당 HTTP 메서드에 맞는 doGet, doPost 등을 실행하는 것이다.

상속받은 클래스는 HTTP 요청을 통해 매핑된 URL이 호출되면 서블릿 컨테이너는 오버라이드된 service 메서드를 실행한다.

한 번 실행해 보자. http://localhost:8080/hello?username=minsu

HelloServlet.service
request = org.apache.catalina.connector.RequestFacade@57de52cc
response = org.apache.catalina.connector.ResponseFacade@4782674a
username = minsu

 

더 자세한 정보를 확인하고 싶다면 application.properties에 해당 내용을 추가해 주면 된다.

logging.level.org.apache.coyote.http11=debug

WAS의 구조는 다음과 같다.

스프링 부트 실행 시 정의한 helloServlet을 생성한다.

helloServlet에 매핑한 URL로 클라이언트 요청이 오면 Request와 Response 객체를 생성하고, helloServlet의 서비스를 실행한다.

 

Reference

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1/dashboard

 

스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 - 인프런 | 강의

웹 애플리케이션을 개발할 때 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. 스프링 MVC의 핵심 원리와 구조를 이해하고, 더 깊이있는 백엔드 개발자로 성장할 수 있습니다., 원

www.inflearn.com

 

 

'Back-End > Spring' 카테고리의 다른 글

빈 스코프  (0) 2023.09.08
빈 생명주기 콜백  (0) 2023.09.08
컴포넌트 스캔  (0) 2023.09.08
싱글톤 패턴과 싱글톤 컨테이너  (0) 2023.09.08
스프링 핵심 원리 -2  (0) 2023.09.01

빈 스코프란?

앞서서

2023.09.08 - [Back-End/Spring] - 빈 생명주기 콜백에서 스프링 빈은 기본적으로 다음과 같은 이벤트 라이프 사이클을 갖는다고 했다.
스프링 컨테이너 생성 -> 스프링 빈 생성 -> 의존관계 주입 -> 초기화 콜백 -> 사용 -> 소멸 전 콜백 -> 스프링 종료

하지만 이는 스프링 빈이 기본적으로 싱글톤 스코프로 생성되기 때문이다.

스코프는 빈이 존재할 수 있는 범위를 뜻하며, 스프링은 싱글톤 빈 외에도 다양한 스코프를 지원한다.

 

  • 싱글톤 스코프
    - 기본 스코프, 스프링 컨테이너의 시작과 종료까지 유지되는 가장 넓은 범위의 스코프
  • 프로토타입 스코프
    - 스프링 컨테이너는 빈의 생성과 의존관계 주입만 관여하고, 더는 관리하지 않는 매우 짧은 스코프
  • 웹 스코프

먼저 싱글톤 스코프로 싱글톤 빈을 생성하여 테스트해보자.

싱글톤 스코프는 다음과 같은 문법을 사용하면 된다.(생략 시에도 기본적으로 스프링 컨테이너는 싱글톤 빈으로 생성한다)

@Scope("singleton")
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Scope;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import static org.assertj.core.api.Assertions.assertThat;

public class SingletonTest {
    @Test
    public void singletonBeanFind(){
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SingletonBean.class);
        SingletonBean singletonBean = ac.getBean(SingletonBean.class);
        SingletonBean singletonBean2 = ac.getBean(SingletonBean.class);
        System.out.println("singletonBean = " + singletonBean);
        System.out.println("singletonBean2 = " + singletonBean2);
        assertThat(singletonBean).isSameAs(singletonBean2);
    }

    @Scope("singleton")
    static class SingletonBean{
        @PostConstruct
        public void init(){
            System.out.println("SingletonBean init");
        }
        @PreDestroy
        public void destroy(){
            System.out.println("SingletonBean destroy");
        }
    }
}

테스트를 실행해 보자.

테스트 실행 시 빈이 같은 인스턴스임을 알 수 있으며,
빈 생성 뒤, 초기화하는 @PostConstruct과 빈 소멸 직전에 실행하는 @PreDestroy 또한 잘 실행된 걸 확인할 수 있다.

 

다음은 프로토타입 스코프로 빈을 생성해 보자.

import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Scope;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import static org.assertj.core.api.Assertions.assertThat;

public class PrototypeTest {
    @Test
    public void prototypeBeanFindTest(){
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(PrototypeBean.class);
        PrototypeBean prototypeBean = ac.getBean(PrototypeBean.class);
        PrototypeBean prototypeBean1 = ac.getBean(PrototypeBean.class);
        System.out.println("prototype = " + prototypeBean);
        System.out.println("prototype = " + prototypeBean1);
        assertThat(prototypeBean).isNotSameAs(prototypeBean1);

        ac.close();
    }

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

    }
}

기존 싱글톤 스코프 방식에서 애노테이션만 수정하였다.

@Scope("prototype")

테스트를 실행해 보자

실행 결과를 보면 프로토타입 스코프의 빈은 스프링 컨테이너에서 빈을 조회할 때 생성되고, 초기화 메서드도 실행된다.

프로토 타입 빈을 2번 조회했으므로, 다른 스프링 빈이 2번 실행되고 초기화도 2번 실행된 걸 알 수 있다.

그리고 ac.close()를 통해 종료를 선언했음에도 스프링 종료 직전에 실행되는 @PreDestroy 메서드가 실행되지 않았다.

 

이를 통해 프로토타입 스코프 빈은 요청할 때마다 새로운 빈이 생성되며,
스프링 컨테이너가 빈의 생성, 의존관계 주입, 초기화까지만 관여하고 더는 관리하지 않는다는 사실을 알 수 있다.

따라서 프로토타입 스코프 빈은 종료 메서드 호출 또한 클라이언트가 직접 해야 한다.

 

다음은 웹 스코프이다.

  • request
    - HTTP 요청 하나가 들어오고 나갈 때까지 유지되는 스코프, 각각의 HTTP 요청마다 별도의 빈 인스턴스가 생성되고, 관리된다.
  • session
    - HTTP Session과 동일한 생명주기를 가지는 스코프
  • application
    - 웹의 서블릿 콘텍스트와 같은 범위로 유지되는 스코프
  • websocket
    -웹 소켓과 동일한 생명주기를 가지는 스코프

해당 글에서는 request 스코프로 테스트 코드를 작성할 것이다.

request 스코프는 여러 http 요청이 동시에 왔을 때, 어떤 요청이 남긴 지 구분하기 어려울 때 쓰면 좋은 스코프이다.


먼저 build.gradle에 추가한다.

implementation('org.springframework.boot:spring-boot-starter-web')

먼저 로그를 남기기 위한 클래스를 작성해 보자.

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
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" +uuid+ "]["+requestUrl+"]" + message);
    }
    @PostConstruct
    public void init(){
        uuid = UUID.randomUUID().toString();
        System.out.println(this);
    }
    @PreDestroy
    public void close(){
        System.out.println(this);
    }
}

@Scope(value = "request")를 사용해서 request 스코프로 지정했다.
이제 이 빈은 HTTP 요청 당 하나씩 생성되고, HTTP 요청이 끝나는 시점에 소멸된다.

이 빈이 생성되는 시점에 자동으로 @PostConstruct 초기화 메서드를 사용해서 uuid를 생성해서 저장해 둔다.

이 빈은 HTTP 요청 당 하나씩 생성되므로, uuid를 저장해 두면 다른 HTTP 요청과 구분할 수 있다.

이 빈이 소멸되는 시점에 @PreDestroy를 사용해서 종료 메시지를 남긴다.

requestURL은 이 빈이 생성되는 시점에는 알 수 없으므로, 외부에서 setter로 입력받는다.

 

테스트용 컨트롤러와 서비스를 작성한다.

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;

@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.getClass());
        myLogger.setRequestUrl(requestURL);
        myLogger.log("controller");
        logDemoService.logic("testId");
        
        return "OK";

    }
}
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class LogDemoService {

    private final MyLogger myLogger;
    public void logic(String id){
        myLogger.log("service Id = " + id);
    }
}

실행 시 다음과 같이 에러가 발생한다.

Error creating bean with name 'myLogger': 
Scope 'request' is not active for the current thread; 
consider defining a scoped proxy for this bean 
if you intend to refer to it from a singleton;

싱글톤 빈은 스프링 애플리케이션을 실행 시에 빈을 생성하고 의존 관계를 주입한다.
하지만 request 스코프 빈은 위에서 http 요청이 들어와야 빈을 생성한다고 했다.
따라서 생성되지 않은 빈을 조회하려고 하여 에러가 발생했다.
이 문제를 해결하기 위해서는 객체를 조회할 때까지 빈 생성을 지연해야 한다.

해당 문제를 해결하기 위해서는 두 가지 방안이 있다.

하나는 ObjectProvider를 사용하는 방법이다.

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;
import javax.servlet.http.HttpServletRequest;

@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();
        System.out.println(myLogger.getClass());
        myLogger.setRequestUrl(requestURL);
        myLogger.log("controller");
        logDemoService.logic("testId");
        
        return "OK";

    }
}
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class LogDemoService {

    private final ObjectProvider<MyLogger> myLoggerProvider;
    public void logic(String id){
        MyLogger myLogger = myLoggerProvider.getObject();
        myLogger.log("service Id = " + id);
    }
}

ObjectProvider를 사용하면 빈의 생성을 myLoggerProvider.getObject()가 호출될 때까지 지연시킬 수 있다.

스프링 애플리케이션을 실행하면 정상적으로 실행되고, api를 호출해 보면 다음과 같은 결과를 얻을 수 있다.

 

한 가지 방법이 더 있는데 프락시 방식이다.

수정한 ObjectProvider를 원래대로 돌린 뒤 스코프 애노테이션에 해당 내용을 추가한다.

@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyLogger {

만약 적용 대상이 클래스면

proxyMode = ScopedProxyMode.TARGET_CLASS

적용 대상이 인터페이스면 다음과 같이 선언하면 된다.

proxyMode = ScopedProxyMode.INTERFACES

다시 스프링 애플리케이션을 실행하고, api를 호출해 보자.

잘 동작하는 것을 확인할 수 있다.

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

에서 출력된 첫 번째 줄을 보면 CGLIB라는 내용을 볼 수 있는데
프록시는 CGLIB라는 라이브러리로 내 클래스를 상속 받은 가짜 프록시 객체를 만들어서 주입한다.


@Scope의 proxyMode = ScopedProxyMode.TARGET_CLASS)를 설정하면

스프링 컨테이너는 CGLIB라는 바이트코드를 조작하는 라이브러리를 사용해서, MyLogger를 상속받은 가짜 프록시 객체를 생성한다.

가짜 프록시 객체는 요청이 오면 그때 내부에서 진짜 빈을 요청하는 위임 로직이 들어있다.
가짜 프록시 객체는 실제 request scope와는 관계가 없다. 그냥 가짜이고, 내부에 단순한 위임 로직만 있고, 싱글톤처럼 동작한다.

 

프록시 객체 덕분에 클라이언트는 마치 싱글톤 빈을 사용하듯이 편리하게 request scope를 사용할 수 있다.

하지만 싱글톤 패턴처럼 사용하는 것 같지만 실제로는 아니기 때문에 주의가 필요하다.
이러한 특별한 스코프는 꼭 필요한 곳에만 최소화하여 사용해야 유지보수 시에 어려움을 겪지 않는다.
또한 웹 스코프가 아니어도 프록시 객체는 사용할 수 있다.

 

'Back-End > Spring' 카테고리의 다른 글

서블릿(Servlet)과 Spring Boot  (0) 2023.10.17
빈 생명주기 콜백  (0) 2023.09.08
컴포넌트 스캔  (0) 2023.09.08
싱글톤 패턴과 싱글톤 컨테이너  (0) 2023.09.08
스프링 핵심 원리 -2  (0) 2023.09.01

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

애플리케이션 종료 시점에 연결을 모두 종료하는 작업을 진행하려면, 객체의 초기화와 종료 작업이 필요하다.
스프링을 통해 이러한 초기화 작업과 종료 작업을 어떻게 진행하는지 예제로 알아보자.

 

스프링 빈은 다음과 같은 사이클을 가진다.

 

객체를 생성하고, 의존 관계를 주입한다.

 

스프링 빈은 의존 관계 주입이 끝나야 필요한 데이터를 사용할 수 있는 준비가 완료되는데

그렇다면 초기화 작업은 의존관계가 모두 주입된 후에 호출되어야 한다.


그런데 의존관계 주입이 끝난 걸 어떻게 알 수 있을까?

 

스프링은 의존관계 주입이 완료되면 스프링 빈에게 콜백 메서드를 통해 초기화 시점을 알려주는 다양한 기능을 제공하며,
스프링 컨테이너가 소멸 직전에 소멸 콜백을 준다.

 

스프링 빈의 이벤트 라이프 사이클은 다음과 같다.

스프링 컨테이너 생성 -> 스프링 빈 생성 -> 의존관계 주입 -> 초기화 콜백 -> 사용 -> 소멸 전 콜백 -> 스프링 종료

 

참고로 모든 빈이 이런 사이클을 따르지 않는다.
싱글톤 빈은 위의 라이프 사이클대로 진행되지만 웹 스코프프로토타입 스코프로 인해 생성되는 빈들은 짧은 생명 주기를 가진다.

 

스프링은 크게 3가지 방법으로 빈 생명주기 콜백을 지원한다.

 

  • 인터페이스(InitializingBean, DisposableBean) 
  • 설정 정보에 초기화 메서드, 종료 메서드 지정 
  • @PostConstruct, @PreDestroy 애노테이션 지원

여기서는 3번째 방법을 서술하려고 한다.

 

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

public class NetworkClient{
    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);
    }

    @PostConstruct
    public void init() throws Exception {
        connect();
        call("초기화 연결 메시지");
    }
    @PreDestroy
    public void close() throws Exception {
        disconnect();
    }

}

@PostConstruct 애노테이션은 초기화 작업을 수행하는 메서드에 사용되며, 해당 메서드는 빈이 생성된 후에 실행된다.
@PreDestroy 애노테이션은 빈 소멸 전에 작업을 수행하는 메서드에 사용 되며, 해당 메서드는 빈이 소멸되기 전에 실행된다.

 

해당 방식은 여러가지 장점이 있다.

우선 최신 스프링에서 제일 권장되는 방식이며, 애노테이션 하나만 붙이면 되므로 매우 편리하다.

또한 임포트된 걸 보면 스프링에 종속된 기술이 아닌 JSR-250이라는 자바 표준이다.

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

자바 표준이므로 스프링이 아닌 다른 컨테이너에서도 사용이 가능하다.

 

단점은 외부 라이브러리에는 적용하지 못한다는 점인데 코드를 고칠 수 없는 외부 라이브러리를 초기화나 종료해야 한다면

@Bean의 initMethod , destroyMethod를 사용하자.

'Back-End > Spring' 카테고리의 다른 글

서블릿(Servlet)과 Spring Boot  (0) 2023.10.17
빈 스코프  (0) 2023.09.08
컴포넌트 스캔  (0) 2023.09.08
싱글톤 패턴과 싱글톤 컨테이너  (0) 2023.09.08
스프링 핵심 원리 -2  (0) 2023.09.01

컴포넌트 스캔은 스프링에서 자동으로 빈을 등록하고 관리하기 위한 방법이다.

컴포넌트 스캔은 이름 그대로 @Component 애노테이션이 붙은 클래스를 스캔해서 스프링 빈으로 등록한다.

 

컴포넌트 스캔을 사용하려면 먼저 @ComponentScan을 설정 정보에 붙여주면 된다.

컴포넌트 스캔을 사용하면 @Configuration 이 붙은 설정 정보도 자동으로 등록되기 때문에
excludeFilters를 이용해서 설정정보는 컴포넌트 스캔 대상에서 제외했다.

@Configuration
@ComponentScan(
         excludeFilters = @Filter(type = FilterType.ANNOTATION, classes =
 Configuration.class))
public class AutoAppConfig {
}

컴포넌트 스캔이 만약 탐색 시에 모든 자바 클래스를 전부 탐색한다면 시간이 오래 걸릴 것이다.
컴포넌트 스캔은 탐색 위치를 지정할 수 있는데 기본적으로 @ComponentScan이 붙은 설정 정보 클래스가 시작 위치가 된다.

 

 @ComponentScan(
         basePackages = "hello.core",
}

다음과 같이 지정도 가능하다.

권장하는 방법은 패키지 지정을 하지 않고, 설정 정보 클래스의 위치를 최상단에 두는 것을 추천한다.
최근의 스프링 부트 또한 이 방법을 기본적으로 제공한다.

 

컴포넌트 스캔의 대상

컴포넌트 스캔은 @Component 뿐만 아니라 다음과 같은 다른 애노테이션도 추가로 스캔한다.

  • @Controller
  • @Service
  • @Repository
  • @Configuration

실제로 컨트롤러 애노테이션을 확인해 보면

@Component 애노테이션을 포함하고 있다.
다른 애노테이션을 확인해 봐도 마찬가지로 @Component 애노테이션이 존재함을 확인할 수 있다.

'Back-End > Spring' 카테고리의 다른 글

빈 스코프  (0) 2023.09.08
빈 생명주기 콜백  (0) 2023.09.08
싱글톤 패턴과 싱글톤 컨테이너  (0) 2023.09.08
스프링 핵심 원리 -2  (0) 2023.09.01
스프링 핵심 원리 요약 - 1  (0) 2023.08.20

+ Recent posts