728x90

🎈 Web Application

웹서버라는것은 HTTP 프로토콜을 기반으로 소통하고 , 데이터를 주고받을 수 있습니다. 대부분의 현태의 데이터 전송이 가능하고, 서버 간의 데이터를 주고받을 때도 대부분 HTTP를 사용합니다.

웹 서버(Web Server)클라이언트로부터 HTTP 요청을 받아들이고, HTTP 프로토콜을 기반으로 웹 페이지와 다른 리소스를 제공하는 소프트웨어입니다. 웹 서버는 웹 브라우저와 같은 클라이언트가 요청하는 정적인(HTML, CSS, 이미지 등) 웹 페이지를 처리하고 전송하는 역할을 담당합니다.

일반적으로 웹 서버는 HTTP 요청을 받아들이는 기능과 요청에 대한 적절한 응답을 제공하는 기능을 갖추고 있습니다. 웹 서버는 클라이언트와의 통신을 위해 HTTP 프로토콜을 사용하며, HTTP 요청에 대한 처리 결과를 HTTP 응답으로 반환합니다.

웹 애플리케이션 서버(Web Application Server, WAS)동적인 웹 애플리케이션을 실행하고 관리하는 미들웨어 소프트웨어입니다. WAS는 웹 서버의 역할뿐만 아니라 애플리케이션 실행 환경과 데이터베이스와의 통신 등을 담당하여 웹 애플리케이션을 실행하는 데 필요한 여러 기능을 제공합니다.

프로그램 코드를 실행해서 애플리케이션 로직을 수행하기 때문에 동적 HTML 및 HTTP API(REST API)등이 WAS를 통해 제공이 됩니다. (ex. 아파치 톰캣)

  1. 애플리케이션 실행 환경 제공: WAS는 자체적으로 자바 애플리케이션의 실행 환경을 갖추고 있으며, 웹 애플리케이션의 실행과 관리를 담당합니다. 웹 애플리케이션을 실행하기 위해 자바 서블릿과 JSP(JavaServer Pages)와 같은 컴포넌트를 지원합니다.

  2. 스레드 관리 및 트랜잭션 처리: WAS는 여러 클라이언트의 요청을 동시에 처리하기 위해 스레드 풀을 사용하여 스레드를 관리합니다. 또한, 트랜잭션 처리와 같은 다중 작업을 원활하게 수행하도록 지원합니다.

  3. 데이터베이스와의 연동: 웹 애플리케이션은 데이터베이스와의 상호작용이 필요한 경우가 많습니다. WAS는 데이터베이스와의 통신을 지원하여 데이터의 조회, 저장, 갱신 등을 처리할 수 있도록 합니다.

  4. 세션 관리: 웹 애플리케이션은 세션 관리를 통해 사용자의 로그인 상태와 상호작용을 유지합니다. WAS는 세션 관리 기능을 제공하여 사용자의 세션 상태를 효율적으로 관리합니다.

  5. 클러스터링: 대규모 웹 애플리케이션에서는 여러 WAS 인스턴스를 클러스터로 구성하여 부하 분산과 고가용성을 제공할 수 있습니다.

웹 시스템은 WAS - DB만으로도 간단하게 시스템 구성이 가능합니다. WAS는 정적 리소스와 애플리케이션 로직 모두 제공이 가능하기 때문입니다. 하지만, 1개의 WAS를 가지고만 운영을 하게되면
역할이 커지게 되며 서버 과부하의 우려가 있으며, 중요도가 낮은 정적 리소스 때문에 중요한 애플리케이션 로직의 수행이 어려울 수 있습니다.

정적 리소스만 제공하는 웹 서버는 다운이 잘 안되지만 애플리케이션 로직이 동작하는 WAS 서버는 잘 다운됩니다.
WAS나 DB에서 장애가 발생했을 시 WEB 서버가 오류 화면을 제공해줄 수 있습니다.

 


 

📋 서버에서 처리해야 하는 업무

웹 애플리케이션 서버를 직접 구현 시 웹 브라우저가 생성한 요청 HTTP 메세지를 분석합니다. 요청 메세지가 어떠한 형태로 보내졌는지 확인 후 비즈니스 로직 실행을 통해 데이터 베이스에 저장을 요청합니다. 이 후 HTTP 응답 메세지를 시작라인 생성, Header 생성, 메세지 바디에 HTML을 생성한 응답메세지를 만든 후 TCP/IP에 응답메세지를 전달한 후 소켓을 종료합니다.

이러한 많은 과정들을 축약하기 위해 서블릿의 개념이 등장하였습니다. 서블릿을 지원하는 WAS를 사용함으로서 HTTP 요청 메세지를 읽는 과정부터 응답 메세지의 생성까지 전부 진행해줍니다.

 

 

 

 

             
             @WebServlet(name="helloServlet", urlPatterns = "/hello")
                -> urlPatterns(/hello)의 URL이 호출되면 서블릿 코드가 실행됩니다.
                public class HelloServlet extends HttpServlet { -> httpServlet을 상속받습니다.

                    @Override
                    protected void service(HttpServletRequest request, 
						HttpServletResponse response) throws ServletException, IOException {
                        System.out.println("HelloServlet.service");
                                //애플리케이션 로직을 작성합니다.
                }

                **HTTP 요청 정보를 편리하게 사용 할 수 있는 HttpServletRequest
                **HTTP 응답 정보를 편리하게 사용할 수 있는 HttpServletResponse 들을 객체 생성하여 편리하게 사용이 가능합니다.

 

HTTP 요청이 오면 WAS 는 Request, Response 객체를 새로 만들어서 서블릿 객체를 호출해주어 개발자는 Request 객체에서는 HTTP 요청 정보를 편리하게 꺼내서 사용, Response 객체에 담겨있는 HTTP 응답 정보를 편리하게 입력할 수 있게 됩니다. WAS는 Response 객체에 담겨있는 내용을 바탕으로 HTTP 응답 메세지를 생성 후 전달합니다.

서블릿 컨테이너


서블릿 컨테이너(Servlet Container)
웹 애플리케이션 서버(또는 웹 컨테이너)의 일부로서, 서블릿의 생명주기를 관리하고 서블릿을 실행하는 환경을 제공하는 소프트웨어입니다. 톰캣처럼 서블릿을 지원하는 WAS를 서블릿 컨테이너라고 합니다. 서블릿 컨테이너는 클라이언트의 요청에 대해 적절한 서블릿을 생성, 호출,관리하고 서블릿이 요청을 처리한 결과를 클라이언트에게 반환합니다.

서블릿 객체는 싱글톤으로 관리 (객체 한개 생성 후 객체 공유) 클라이언트의 요청이 올 때마다 객체를 생성하는것은 비효율 적이기 때문에 , 최초 로딩 시점에 서블릿 객체를 미리 만들어두고 재사용이 가능하도록 하는 것이 좋습니다. 모든 클라이언트의 요청은 동일한 서블릿 객체 인스턴스에 접근할 수 있으며, 서블릿 컨테이너가 종료 될 때 함께 종료됩니다. (💥 공유 (멤버변수) 변수를 사용할 때 굉장히 주의가 필요)

 


 

동시 요청 - 멀티 쓰레드✍️💡

클라이언트가 요청을 하면 WAS에 요청이 전달되면 TCP/IP 커넥션으로 연결이 되며 서블릿을 호출하게되며 응답을 줄 수 있습니다. 여기서 서블릿 객체를 호출하는 당사자가 굉장히 중요합니다. 바로 쓰레드가 서블릿을 호출하게 됩니다.

 

쓰레드란 ?

쓰레드(Thread)프로세스 내에서 실행되는 실행 단위로, 프로세스의 자원을 공유하면서 동시에 여러 작업을 처리할 수 있도록 해주는 기본적인 실행 단위입니다. 애플리케이션 코드를 하나 하나 순차적으로 실행해주며, 자바 메인 메서드를 처음 실행하면 main 이라는 이름의 쓰레드가 실행되는 것 입니다.

일반적으로 하나의 프로세스는 하나의 메인 쓰레드를 가지고 있고, 이 메인 쓰레드에서 프로그램의 주요 로직이 실행됩니다. 쓰레드가 없다면 자바 애플리케이션 실행이 불가능하며, 쓰레드는 한번에 하나의 코드라인만 수행합니다. 그러나 프로그램이 다중 작업을 동시에 처리해야 할 경우가 있습니다. 이때 추가적인 쓰레드를 생성하여 각 작업을 병렬적으로 처리할 수 있습니다.

 

📋 쓰레드의 단일 요청

단일 쓰레드 모델에서는 프로그램의 모든 작업이 메인 쓰레드에서 순차적으로 실행됩니다. 요청이 오면 1개의 쓰레드를 할당한 후 서블릿 객체를 호출한 후 응답을 전달하고 휴식을 합니다. 한 번에 하나의 작업만 처리할 수 있으며, 다음 작업은 이전 작업이 완료된 후에 실행됩니다. 이러한 실행 모델은 단순하고 직관적이지만, 작업을 순차적으로 처리하기 때문에 성능이 저하될 수 있습니다.

만약 다중 요청 시 쓰레드를 하나만 사용하는데 그 하나의 쓰레드에 장애가 발생하여 처리가 지연되면, 두번째로 들어오는 요청이 대기하게되며 1번째의 요청과 2번째의 요청 모두 장애가 발생하게 될 수 있습니다. 이러한 상황을 해결하기 위해서 요청 마다 쓰레드를 생성하는 방법이 있습니다. (신규 쓰레드 생성)

💥 요청이 올때마다 쓰레드를 생성하는 경우, 동시 요청 처리가 가능하고 리소스 (CPU, 메모리)가 허용될 때까지 처리가 가능합니다. 그리고 하나의 쓰레드가 지연되어도 나머지 쓰레든느 정상 동작하는 장점이 있습니다.

하지만, 쓰레드 생성비용은 비싼편이고, 요청 마다 쓰레드를 생성하면 응답 속도가 늦어집니다. 쓰레드 생성에는 제한이 없기 때문에 고객요청이 너무 많이오면(수강신청 시) , CPU와 메모리가 버티지 못하 임계점을 넘어서 서버가 죽을 수도 있습니다.

 

📋 쓰레드 풀

쓰레드 풀(Thread Pool)쓰레드를 미리 생성하여 풀(pool)에 보관해두고, 필요할 때마다 풀에서 쓰레드를 가져와 작업을 처리하는 기법입니다. 쓰레드 풀은 다수의 클라이언트 요청을 동시에 처리해야 하는 서버 애플리케이션에서 쓰레드의 생성과 소멸에 따른 오버헤드를 줄이고 성능을 향상시키기 위해 사용됩니다.

내부에 쓰레드들을 미리 생성하여 보관하고 있다가 요청이 오게되면 쓰레드 풀로 요청을 받게되고, 풀 내부에 있던 쓰레드들을 사용하여 서블릿 객체를 호출하여 쓸 수 있습니다. 쓰레드의 사용이 다 끝나면 쓰레드 풀에 반납이 가능합니다.

  1. 쓰레드 재사용: 쓰레드 풀은 미리 생성된 쓰레드를 재사용하여 작업을 처리합니다. 작업이 완료되면 해당 쓰레드는 다음 작업을 처리하기 위해 풀에 반환됩니다. 이렇게 함으로써 쓰레드의 생성과 소멸에 따른 오버헤드를 줄이고, 쓰레드를 재사용함으로써 시스템의 부하를 줄일 수 있습니다.
  2. 제한된 쓰레드 수: 쓰레드 풀은 미리 설정된 쓰레드 개수를 가지고 있으며, 이 개수를 초과하여 쓰레드가 생성되지 않도록 관리합니다. 이를 통해 쓰레드 개수를 제한함으로써 서버의 과도한 부하를 방지하고 안정적인 성능을 유지할 수 있습니다.
  3. 쓰레드 생성 비용 절감: 쓰레드 풀은 미리 쓰레드를 생성하여 보관하고 있기 때문에, 새로운 작업이 들어올 때마다 쓰레드를 새로 생성하는 비용을 절감할 수 있습니다. 이는 많은 수의 작업을 처리해야 하는 경우에 특히 효과적입니다.
  4. 작업 대기 처리: 쓰레드 풀에 모든 쓰레드가 사용 중인 경우, 새로운 작업이 들어올 때까지 대기하게 됩니다. 이를 통해 서버의 과부하를 방지하고 안정적인 처리를 유지할 수 있습니다. (톰캣은 최대 200개 기본 설정)

쓰레드가 미리 생성되 어 있으므로, 쓰래드를 생성하고 종료하는 비용(CPU)가 절약되고 , 응답시간이 빠릅니다. 생성 가능한 쓰레드의 최대치가 있으므로 너무 많은 요청이 들어와도 기존 요청은 안전하게 처리할 수 있는 장점이 있습니다.

👉 WAS는 다수의 클라이언트로부터 동시에 여러 요청을 받을 수 있어야 하며, 이러한 요청을 동시에 처리하기 위해 멀티쓰레드를 활용할 수 있습니다. 개발자는 멀티쓰레드 관련 코드를 신경 쓰지 않아도 되고, 싱글 쓰레드 프로그래밍을 하듯이 편리하게 소스코드를 개발할 수 있습니다.
멀티 쓰레드 환경이므로 싱글톤 객체(서블릿, 스프링 빈)을 주의해서 사용해야 합니다.


 

📘 HTML, HTTP API, CSR, SSR이란 ?

백엔드 개발자가 서비스를 제공할 때 고려해야할 3가지 방식

  1. 정적 리소스 - 고정된 HTML 파일 , CSS , JS , 이미지, 영상등을 제공합니다. 주로 웹 브라우저에서 요청을 합니다. 요청이 /hello.html로 요청이 온다면, 이미 생성된 리소스 파일을 전달해줍니다.

  2. HTML 페이지 - WAS가 웹 브라우저에서 요청을 받으면, DB를 통해서 데이터를 조회한 후 동적으로 HTML을 생성 VIEW 템플릿인 JSP나 Thymeleaf등을 이용해서 DB에서 조회된 데이터와 프로그램 코드를 넣어서 HTML 코드를 동적으로 생성한 후 브라우저에게 전달해줍니다.

  3. HTTP API - DB의 데이터를 조회한 후 JSON형식의 데이터를 (KEY:VALUE) 형태의 정보를 전달하게 됩니다. 그럼 브라우저에서 해석이 바로 되진 않기때문에 브라우저에 보여주기 위해 쓰는 것이 아니라, 데이터만 주고 받는 상황에만 주로 사용됩니다.

SSR(서버 사이드 렌더링)CSR(클라이언트 사이드 렌더링)은 웹 애플리케이션의 렌더링 방식을 나타내는 용어입니다. 이 두 가지 방식은 웹 페이지의 내용을 브라우저에 보여주는 방식에 차이가 있습니다.

  1. 서버 사이드 렌더링 (SSR) → JSP , 타임리프 백엔드 개발자
    서버에서 최종 HTML을 생성한 후 클라이언트에게 전달합니다. HTML을 만드는 과정을 전부 서버에서 종료 후 브라우저에 전달합니다. 서버 사이드 렌더링은 웹 서버에서 페이지의 내용을 모두 구성한 후, 클라이언트(브라우저)에게 완전히 렌더링된 HTML을 보내주는 방식입니다. 클라이언트는 렌더링된 HTML을 받아서 바로 화면에 표시할 수 있습니다. 이후에는 필요한 데이터를 요청하고 받아오는 AJAX 방식으로 동작합니다.

    단점 : 서버 부하가 증가할 수 있습니다. 많은 요청을 처리해야 하므로 서버에 부담이 갈 수 있습니다. 페이지 전환이 있을 때마다 서버로부터 새로운 HTML을 받아와야 하므로 네트워크 비용이 증가할 수 있습니다

    장점 : 초기 렌더링 속도가 빠르고, 첫 페이지 로딩이 빠릅니다. SEO(Search Engine Optimization)에 유리하며, 검색 엔진에서 페이지를 크롤링하기 쉽습니다.

  2. 클라이언트 사이드 렌더링 (CSR) → React , Vue.js 프론트엔드 개발자 ****HTML결과를 자바스크립트를 사용해 웹 브라우저에서 동적으로 생성해서 적용합니다. 주로, 동적인 화면에 사용, 웹 환경을 마치 앱처럼 필요한 부분부분을 변경할 수 있습니다. 클라이언트 사이드 렌더링은 초기에 서버로부터 HTML과 필요한 JS, CSS 등의 정적 파일을 받아온 후, 클라이언트(브라우저)에서 JavaScript를 이용하여 동적으로 페이지를 구성하는 방식입니다. 클라이언트가 빈 HTML을 받아오고, 데이터를 요청한 뒤 JavaScript가 데이터를 받아와서 브라우저에 렌더링하는 방식입니다.

    단점 : 초기 렌더링 속도가 느리며, 첫 페이지 로딩이 오래 걸립니다. SEO에 불리하며, 검색 엔진이 페이지를 크롤링하는 데 어려움이 있습니다.

    장점 : 필요한 데이터만 요청하고 받아오므로, 서버 부하가 적습니다. 사용자 경험(UX)이 좋고, 사용자와 상호작용이 많은 웹 애플리케이션에 적합합니다.

 

728x90
반응형
728x90

싱글톤 방식의 주의점

싱글톤 패턴이든 , 스프링 같은 싱글톤 컨테이너를 사용하든, 객체 인스턴스를 하나만 생성해서 공유하는 싱글톤 방식은 여러 클라이언트가 하나의 같은 객체 인스턴스를 공유하기 때문에 싱글톤 객체는 상태를 유지(stateful)하는 설계를 지양합니다.

 

👉 무상태(stateless)로 설계해야 함!

싱글톤 패턴을 사용하는 방식에는 몇 가지 주의해야 할 점이 있습니다. 이러한 주의점을 알고 사용함으로써 문제를 예방하고 싱글톤의 장점을 최대한 활용할 수 있습니다. 주요한 주의점은 다음과 같습니다:

  1. 멀티스레딩 환경에서의 동시성 문제: 싱글톤은 전역 상태를 유지하므로, 멀티스레드 환경에서 여러 스레드가 동시에 접근할 경우 동기화 문제가 발생할 수 있습니다. 따라서 동시성 문제를 방지하기 위해 스레드 안전(Thread-Safe)한 싱글톤 구현 방식을 선택하거나 동기화 메커니즘을 적절히 사용해야 합니다.

  2. 의존성 관리의 어려움: 특정 클라이언트에 의존적인 필드가 있으면 안됩니다. 가급적 값을 수정하면 안되고, 읽기 기능만 가능하도록 해야합니다. 싱글톤은 전역적인 상태를 가지고 있기 때문에 의존성이 복잡한 경우 다른 객체에 대한 의존성을 주입하는 것이 어려울 수 있습니다.

스프링 빈의 필드에 공유 값을 설정하면 정말 큰 장애가 발생할 수 있습니다.

 

	ㅡㅡㅡㅡㅡㅡㅡㅡ주문 정보를 입력받아 출력하는 파일ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

        public class StateFulService {

            private int price; //상태를 유지하는 필드 10000->20000으로 바꿔치기 됨

            public void order(String name, int price) {
                System.out.println("name = " + name + " " +"price = " +  price);
                this.price = price;
            }

            public int getPrice() {
                return price;
            }
        }

        ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡTest 코드가 작성된 파일ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

        class StateFulServiceTest {

            //해당 스프링 컨테이너는 하단에 생성된 TestConfig안의 1개의 Bean만 생성해서 사용


            @Test
            void statefulServiceSingleton() {

                AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);
                StateFulService stateFulService1 = ac.getBean(StateFulService.class);
                StateFulService stateFulService2 = ac.getBean(StateFulService.class);

                //ThreadA : userA사용자는 10000원어치 주문
                stateFulService1.order("userA", 10000);
                //ThreadB : B 사용자는 20000원을 주문
                stateFulService2.order("userB", 20000);

                //ThreadA : 사용자  A가 주문 금액을 조회하고 싶을때 사용하는 메서드 작성
                //A가 주문을 하고, 금액을 조회하는 사이에 B의 주문이 들어온 상황
                int price = stateFulService1.getPrice();
                System.out.println("price = " + price); 
                        //출력 price=20000(중간에 B의 주문이 들어오게 되며 price의 값이 변경)


                //A사용자는 10000원을 주문했는데, 중간의 가격 변경으로 price가 20000으로 변경되었는데도 Test는 통과됨
                assertThat(stateFulService1.getPrice()).isEqualTo(20000);
            }


        ㅡㅡㅡ해당코드에서 임시로 사용할 Bean 생성ㅡㅡㅡ
            static class TestConfig {

                @Bean
                public StateFulService stateFulService() {
                    return new StateFulService();
                }
            }
        }

 

ThreadA가 사용자 A의 코드를 출력하고, ThreadB가 사용자 B의 코드를 출력한다고 가정하에 StateFulService의 price 필드는 공유되는 필드인데, 특정 클라이언트가 중간에 값을 변경하는 상황이 발생하게 됩니다.
이 때, 공유된 필드값을 같이 사용하게되면서 사용자 A의 주문 금액은 10000원이 되어야 하는데, 20000원이라는 결과가 나오게 되었습니다. 공유필드의 사용은 주의해서 사용해야 하며 스프링 빈은 항상 무상태(stateless)로 설계해야 합니다.


👉 @Configuration과 싱글톤의 관계

 

@Configuration은 스프링 프레임워크에서 사용되는 어노테이션으로, 스프링 빈(Bean) 구성 클래스를 나타냅니다. @Configuration 어노테이션이 지정된 클래스는 스프링 컨테이너에서 빈으로 인식되고, 해당 클래스 내의 @Bean 어노테이션이 지정된 메서드들이 스프링 빈으로 등록됩니다.

@Configuration을 사용하는 이유는 다음과 같습니다:

  1. 스프링 빈 등록: @Configuration 어노테이션이 지정된 클래스에서 @Bean 어노테이션이 지정된 메서드들은 스프링 컨테이너에 의해 관리되는 빈으로 등록됩니다. 이를 통해 객체의 인스턴스화, 의존성 주입, 라이프사이클 관리 등을 스프링이 담당하게 됩니다.

  2. 컨테이너 설정: @Configuration 어노테이션이 지정된 클래스는 스프링 컨테이너의 설정 파일 역할을 수행합니다. 스프링은 @Configuration 어노테이션이 지정된 클래스를 스캔하여 컨테이너의 설정 정보로 활용하며, 이를 통해 스프링 빈들의 관계와 동작 방식을 구성할 수 있습니다.

@Configuration 어노테이션을 사용하여 스프링 빈을 등록할 때 주의해야 할 점은, 기본적으로 스프링은 등록된 빈들을 싱글톤(Singleton)으로 관리한다는 것입니다.
즉, @Configuration 어노테이션이 지정된 클래스 내의 @Bean 어노테이션이 지정된 메서드들이 반환하는 객체들은 스프링 컨테이너 내에서 단 하나의 인스턴스만 생성되고 공유됩니다.

따라서, @Configuration 어노테이션을 사용하여 빈을 등록하면 해당 빈들은 싱글톤 패턴으로 동작하게 됩니다.
이는 애플리케이션 전체에서 객체를 공유하고 메모리 사용을 최적화하는 장점을 가지지만, 주의해야 할 점도 있습니다. 싱글톤으로 등록된 빈은
멀티스레드 환경에서 동시에 접근할 경우 동기화 문제가 발생할 수 있으므로, 동시성에 대한 주의가 필요합니다.

 

 

728x90
반응형
728x90

스프링은 대부분 기업용 온라인 서비스 기술을 지원하기 위해 만들어졌습니다. 스프링 애플리케이션의 대부분은 웹 애플리케이션이며, 웹 애플리케이션은 보통 여러 고객이 동시에 요청하는 경우가 많습니다.

웹 애플리케이션의 특징은 고객의 요청이 많고 빈번합니다.

웹 애플리케이션과 싱글톤은 서로 다른 개념입니다. 각각에 대해 간단히 설명해드리겠습니다.

  1. 웹 애플리케이션(Web Application) 웹 애플리케이션은 인터넷 브라우저를 통해 접근할 수 있는 애플리케이션입니다. 일반적으로 웹 애플리케이션은 클라이언트-서버 아키텍처를 따르며, 웹 브라우저에서 요청을 보내고 서버에서 처리한 결과를 다시 브라우저로 응답하는 방식으로 동작합니다. 웹 애플리케이션은 주로 HTML, CSS, JavaScript 등을 사용하여 사용자 인터페이스를 제공하고, 서버 측에서는 서버 사이드 프레임워크를 사용하여 비즈니스 로직을 처리합니다.
  2. 싱글톤(Singleton) 싱글톤은 디자인 패턴 중 하나로, 애플리케이션에서 특정 클래스의 인스턴스를 오직 하나만 생성하고 사용하는 것을 의미합니다. 이렇게 하면 여러 곳에서 동일한 인스턴스에 접근하여 상태를 공유하고 객체 간의 일관성을 유지할 수 있습니다. 싱글톤은 주로 자원의 공유나 중복 생성을 피하기 위해 사용됩니다. 스프링 프레임워크에서는 빈(Bean)의 기본 스코프로 싱글톤을 사용하며, 컨테이너 내에서 하나의 빈 인스턴스를 생성하고 관리합니다.

따라서 웹 애플리케이션은 웹 환경에서 동작하는 애플리케이션을 의미하고, 싱글톤은 객체의 인스턴스 생성과 관리 방식 중 하나로 애플리케이션에서 공유되는 유일한 인스턴스를 생성하는 패턴입니다. 웹 애플리케이션에서도 싱글톤 패턴을 활용하여 객체를 생성하고 관리할 수 있습니다.

      
      public class SingletonTest {
            @Test
            @DisplayName("스프링 없는 순수한 DI 컨테이너")
            void pureContainer() {
                //DI컨테이너에 있는 Bean객체의 사용이 필요하여 AppConfig 객체를 생성
                AppConfig appConfig = new AppConfig();

                //1. 조회: 호출할 때마다 객체를 생성하는지 조회
                MemberService memberService1 = appConfig.memberService();

                //2. 조회: 호출할 때마다 객체를 생성하는지 조회
                MemberService memberService2 = appConfig.memberService();

                //참조 값이 다른 것을 확인해 보도록 합니다.
                //AppcConfig한테서 memberService를 호출을 힐 때마다 참조값(주소가) 다르게 호출
                System.out.println("memberService1 = " + memberService1);
                System.out.println("memberService2 = " + memberService2);
            }
        }
        

        출력 : memberService1 = Spring.core.member.MemberServiceImpl@5dda768f
               memberService2 = Spring.core.member.MemberServiceImpl@7a8c8dcf







ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

스프링 없는 순수한 DI 컨테이너인 AppConfig는 요청을 할 때마다 객체를 새로 생성합니다. 고객 트래픽이 초당 100이 나오면 100개의 객체가 생성되고 소멸됩니다. 메모리 낭비가 심하게 발생합니다. 해결 방안으로는 해당 객체가 딱 1개만 생성되고, 공유하도록 설계하기 위해 싱글톤 패턴이 등장하였습니다.


 

👉 싱글톤 패턴

싱글톤 패턴(Singleton Pattern)소프트웨어 디자인 패턴 중 하나로, 클래스의 인스턴스가 단 하나만 생성되도록 보장하는 패턴입니다. 즉, 해당 클래스의 인스턴스를 전역적으로 접근 가능하고 재사용할 수 있습니다. 객체 인스턴스를 2개 이상 생성하지 못하도록 막아야 하며, private 생성자를 사용해서 외부에서 임의로 new 키워드를 사용하지 못하도록 막아야 합니다.

 

 

🎈 Singleton객체를 생성해보고, Test를 진행해보자

      
      public class SingletonService {

            //1. static 영역에 객체를 딱 1개만 생성해둔다.
            //자기 자신을 내부에 static private으로 선언하고 가지고 있게 됨
            //(클래스 레벨에 올라가게 되면서 딱 하나만 존재)
            
            private static final SingletonService instance = new SingletonService();

            //JVM이 실행될 때, 바로 싱글톤 서비스의 스태틱 영역에 자기 자신의 객체를 생성하여 인스턴스를 담음
            //2. public으로 열어서 객체 인스턴스가 필요하면 이 static 메서드를 통해서만 조회하도록 허용한다.
            public static SingletonService getInstance() {
                return instance;
            }

            //3. 생성자를 private로 선언해서 외부에서 new 키워드를 사용한 객체 생성을 못하게 음
            private SingletonService() {
            }
            

            public void logic() {
                System.out.println("싱글톤 객체 로직 호출");
            }
        }




  ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡSingleTon 객체 생성 Test 코드ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
  

            @Test
            @DisplayName("싱글톤 패턴을 적용한 객체 사용")
            void singletonServiceTest() {
                SingletonService singletonService1 = SingletonService.getInstance();
                System.out.println("singletonService1 = " + singletonService1);

                SingletonService singletonService2 = SingletonService.getInstance();
                System.out.println("singletonService2 = " + singletonService2);

                assertThat(singletonService1).isSameAs(singletonService2);
                
          // same == : 두 객체가 정확히 동일한 객체인지를 확인 / 두 객체가 메모리에서 같은 위치를 참조하는지를 비교
          // equal : 두 객체의 값이 동등한지를 확인 / 객체의 실제 값을 비교하여 동등성을 판단
            }
            

        출력 : singletonService1 = Spring.core.singleton.SingletonService@55fe41ea
               singletonService2 = Spring.core.singleton.SingletonService@55fe41ea

 

  1. static 영역에 객체 instance를 미리 하나 생성해서 올려둡니다. 이 객체 인스턴스가 필요하면 오직 getInstance() 메서드를 통해서만 조회할 수 있습니다.
  2. getInstance() 메서드를 호출하면 항상 같은 인스턴스를 반환합니다. 딱 1개의 객체 인스턴스만 존재해야 하므로, 생성자를 private으로 막아서 혹시라도 외부에서 new 키워드로 객체 인스턴스가 생성되는 것을 막습니다.

싱글톤 패턴은 다음과 같은 특징을 가집니다:

  1. 단일 인스턴스: 싱글톤 패턴은 클래스의 인스턴스를 하나만 생성합니다. 이 인스턴스는 애플리케이션 전체에서 공유되어 사용됩니다.
  2. 전역 접근: 싱글톤 인스턴스는 어디서든 전역적으로 접근할 수 있습니다. 다른 객체들은 해당 인스턴스를 사용하여 데이터를 공유하거나 해당 인스턴스의 메서드를 호출할 수 있습니다.
  3. 지연 로딩: 일반적으로 싱글톤 인스턴스는 처음 필요할 때 생성됩니다. 이를 지연 로딩(Lazy Loading)이라고 합니다. 따라서 인스턴스 생성 비용을 줄이고 필요할 때만 생성하여 자원을 효율적으로 관리할 수 있습니다.

싱글톤 패턴은 다양한 상황에서 활용될 수 있습니다. 예를 들어, 데이터베이스 연결, 로깅, 캐시 등과 같이 애플리케이션 전체에서 공유되어야 하는 객체들을 관리하기 위해 싱글톤 패턴이 사용될 수 있습니다.

싱글톤 패턴을 구현하기 위해서는 다양한 방법이 있지만, 대표적으로는 다음과 같은 방식을 사용합니다:

  1. 정적 멤버 변수: 클래스 내부에 정적 멤버 변수를 선언하고, 해당 변수에 인스턴스를 생성하여 저장합니다.
  2. 지연 초기화: 인스턴스 생성을 처음 요청받을 때까지 지연시키고, 해당 요청 시에만 인스턴스를 생성합니다.
  3. 스레드 안전성: 멀티스레드 환경에서의 동시 접근 문제를 해결하기 위해 동기화 처리를 추가합니다.

싱글톤 패턴의 단점으로는
1. 싱글톤 패턴을 구현하는 코드가 길어지게 됩니다.
2. 의존 관계상 클라이언트가 구체 클래스에 의존합니다. (DIP를 위반하게 됩니다.)
3. 클라이언트가 구체 클레스에 의존해서 OCP 원칙을 위반할 가능성이 높고, 테스트 하기가 어렵습니다.
4. 내부 속성을 변경하거나 초기화 하기 어렵고, private 생성자로서 자식 클래스를 만들기 어렵습니다. 그 결과 유연성이 떨어지며, 안티 패턴으로 불리기도 합니다.


📄 싱글톤 컨테이너

 

스프링 컨테이너는 싱글톤 패턴의 문제점을 해결하면서 객체 인스턴스를 싱글톤(1개만 생성)으로 관리합니다. 스프링 빈이 바로 싱글톤으로 관리되는 빈을 의미합니다.

싱글톤 컨테이너(Singleton Container)는 객체를 싱글톤 패턴으로 관리하는 디자인 패턴과 관련된 개념입니다. 싱글톤 패턴은 애플리케이션 전체에서 단 하나의 인스턴스만 생성하여 공유하고, 이를 통해 객체 생성과 메모리 사용을 최적화하는 패턴입니다. 싱글톤 객체를 생성하고 관리하는 기능을 싱글톤 레지스트리라고 합니다.

싱글톤 컨테이너는 이러한 싱글톤 패턴을 구현하는데 사용되는 컨테이너입니다. 스프링 컨테이너는 싱글톤 컨테이너의 역할을 합니다. 주로 의존성 주입(Dependency Injection)을 사용하는 프레임워크나 라이브러리에서 제공되며, 객체의 인스턴스를 관리하고 필요한 곳에서 공유할 수 있도록 지원합니다.

싱글톤 컨테이너의 주요 특징은 다음과 같습니다:

  1. 객체 인스턴스의 단일성: 싱글톤 컨테이너는 애플리케이션 전체에서 단 하나의 인스턴스만 생성하여 사용합니다. 이를 통해 여러 곳에서 동일한 객체를 공유하고, 객체 생성과 소멸에 따른 오버헤드를 줄일 수 있습니다.

  2. 라이프사이클 관리: 싱글톤 컨테이너는 객체의 라이프사이클을 관리합니다. 객체의 생성, 초기화, 소멸 등의 작업을 컨테이너가 담당하므로, 개발자는 객체의 라이프사이클에 대해 직접 관리할 필요가 없습니다.

  3. 의존성 주입: 싱글톤 컨테이너는 의존성 주입(Dependency Injection)을 통해 객체 간의 의존성을 관리합니다. 다른 객체에 대한 의존성을 주입받을 수 있으며, 이를 통해 코드의 유연성과 테스트 용이성을 개선할 수 있습니다.

싱글톤 컨테이너는 대표적으로 스프링(Spring) 프레임워크에서 제공하는 스프링 컨테이너가 있습니다. 스프링 컨테이너는 객체의 싱글톤 패턴을 지원하며, @Component, @Service, @Repository 등의 어노테이션을 사용하여 싱글톤 빈을 등록하고 관리할 수 있습니다. 이를 통해 스프링은 객체의 라이프사이클을 관리하고, 필요한 곳에서 싱글톤 빈을 주입하여 사용할 수 있도록 지원합니다.

728x90
반응형
728x90

 

빈 팩토리(Bean Factory)와 애플리케이션 컨텍스트(Application Context)는 스프링 프레임워크에서 빈(Bean)을 생성하고 관리하는 기능을 제공하는 핵심 컨테이너입니다. 하지만 두 개의 용어는 약간의 차이점이 있습니다.

  1. 빈 팩토리(Bean Factory):
    • 빈 팩토리는 스프링의 핵심 컨테이너로서 빈(Bean)의 생성, 관리, 의존성 주입(Dependency Injection), 라이프사이클 관리 등의 기능을 제공합니다.
    • 주요 인터페이스로는 BeanFactory가 있으며, DefaultListableBeanFactory와 같은 구현체가 사용됩니다.
    • 빈 팩토리는 스프링의 가장 기본적인 컨테이너로서, 간단한 빈 관리 기능을 제공합니다.

  2. 애플리케이션 컨텍스트(Application Context):
    • 애플리케이션 컨텍스트는 빈 팩토리의 기능을 확장한 컨테이너로서, 빈 팩토리의 모든 기능을 포함하며 추가적인 기능을 제공합니다.
    • 주요 인터페이스로는 ApplicationContext가 있으며, AnnotationConfigApplicationContext, ClassPathXmlApplicationContext, WebApplicationContext 등이 사용됩니다.
    • 메세지 소스를 활용한 국제화 기능 - 한국에서 들어오면 한국어, 영어권에서 들어오면 영어로 출력해주는 기능입니다. ( 국제화(i18n) 지원 )
    • 환경변수 - 로컬, 개발, 운영등을 구분해서처리합니다.
    • 애플리케이션 이벤트 - 이벤트 발행 및 구독하는 모델을 편리하게 지원합니다.
    • 편리한 리소스 조회 - 파일이나 클래스 패스같은 외부 url 같은 곳에서 파일을 불러와서 쓸 때, 편하게 사용할 수 있는 기능을 제공합니다.
    • 또한, 애플리케이션 컨텍스트는 빈을 지연 로딩(lazy-loading)하거나 프로토타입 스코프의 빈을 여러 번 생성하는 등의 더 복잡한 빈 관리 기능을 제공합니다.

즉, 빈 팩토리는 스프링의 가장 기본적인 빈 관리 기능을 제공하는 컨테이너이며, 애플리케이션 컨텍스트는 빈 팩토리의 모든 기능을 포함하고 추가적인 기능을 제공하는 확장된 형태의 컨테이너입니다. 애플리케이션 개발 시 일반적으로 애플리케이션 컨텍스트를 사용하는 것이 더 편리하며, 빈 팩토리는 특별한 상황에서나 좀 더 가볍게 사용될 수 있습니다.'


XML로 만드는 Bean

 

xml기반의 appconfig.xml 스프링 설정정보와 코드로 된 Appconfig.java 설정 정보를 비교해보면 유사하다는 걸 알 수 있습니다. xml 기반으로 설정하는 것은 최근에 잘 사용하지 않으므로 필요하면 공식 문서를 확인해보시면 좋습니다. https://spring.io/projects/spring-framework

        👉 XML로 bean 생성하는 코드 (.xml파일)
        
        <?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

        <bean id="memberService" class="Spring.core.member.MemberServiceImpl">
            <constructor-arg name ="memberRepository" ref="memberRepository" />

        </bean>
        <bean id="memberRepository" class="Spring.core.member.MemoryMemberRepository"/>
        <bean id="orderService" class="Spring.core.order.OrderServiceImpl" >
            <constructor-arg name="memberRepository" ref="memberRepository" />
            <constructor-arg name="discountPolicy" ref="discountPolicy"/>
        </bean>
        <bean id="discountPolicy" class="Spring.core.discount.RateDiscountPolicy"/>
</beans>



        👉 xml 형식의 bean을 소환하여 작성한 TEST 코드 test 실행

        public class XmlAppContext {

            @Test
            void XmlAppContext() {
                ApplicationContext ac = new GenericXmlApplicationContext("appconfig.xml");
                MemberService memberService = ac.getBean("memberService", MemberService.class);
                assertThat(memberService).isInstanceOf(MemberService.class);
            }
        }

테스트코드가 실행이 완료되며, xml로 입력된 bean이 생성됨을 확인할 수 있습니다. 스프링의 싱글톤 빈으로 생성이 완료되었습니다

 

스프링 빈 메타 정보 - BeanDefinition

스프링에서 빈 메타 정보는 BeanDefinition이라는 개념을 통해 표현됩니다. BeanDefinition스프링 컨테이너가 빈(Bean)을 생성하고 관리하기 위해 필요한 정보를 담고 있는 객체입니다. 앞서 설명한 역할과 구현을 개념적으로 나눈 것입니다. BeanDefinition을 설정 메타 정보라 하고, @Bean 또는 <Bean> 당 각각 하나씩 메타정보가 생성됩니다. 스프링 컨테이너는 이 메타정보를 기반으로 스프링 빈을 생성합니다.

다음은 BeanDefinition에 대해 알아보는데 도움이 될 수 있는 설명입니다

  1. 빈의 클래스 타입: BeanDefinition은 빈으로 생성될 클래스의 타입 정보를 가지고 있습니다. 이는 스프링 컨테이너가 해당 클래스를 인스턴스화하고 빈으로 등록할 때 사용됩니다.
  2. 빈의 범위(Scope): BeanDefinition은 빈의 범위를 지정하는 정보를 가지고 있습니다. 스프링은 다양한 범위를 지원하는데, 주로 싱글톤(Singleton)과 프로토타입(Prototype)이 가장 많이 사용됩니다.
  3. 빈의 의존성(Dependency): BeanDefinition은 빈이 의존하는 다른 빈들과의 관계를 나타내는 정보를 가지고 있습니다. 이를 통해 스프링 컨테이너는 의존성 주입(Dependency Injection)을 수행하여 빈들 간의 관계를 자동으로 설정할 수 있습니다.
  4. 초기화 메서드와 소멸 메서드: BeanDefinition은 빈의 초기화와 소멸에 관련된 메서드 정보를 가지고 있습니다. 이를 통해 스프링 컨테이너는 빈의 라이프사이클을 관리하며, 빈이 생성될 때 초기화 메서드를 호출하고 소멸될 때 소멸 메서드를 호출할 수 있습니다.
  5. 추가 속성과 설정 정보: **BeanDefinition**은 다양한 추가 속성과 설정 정보를 포함할 수 있습니다. 예를 들어, 빈의 이름, 별칭, 프로파일, 지연 로딩 여부 등의 정보를 포함할 수 있습니다.

BeanDefinition은 스프링 컨테이너가 빈을 생성하고 관리하는 중요한 메타 정보를 담고 있습니다. 이를 통해 스프링은 빈의 생성, 초기화, 의존성 주입 등을 자동으로 처리할 수 있고, 개발자는 빈의 구체적인 설정과 관계를 중심으로 애플리케이션을 개발할 수 있습니다.

        👉 **스프링 빈 메타 정보를 확인해 보는 코드 작성**

        public class BeanDefinitionTest {

            AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig .class);

            @Test
            @DisplayName("빈 설정 메타 정보 확인")
            void findApplicationBean() {
                String[] beanDefinitionNames = ac.getBeanDefinitionNames();
                for (String beanDefinitionName : beanDefinitionNames) {
                    BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName);

                    if (beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION) {
                        System.out.println("beanDefinition = " + beanDefinition + "beanDefinition = " + beanDefinition);
                    }
                }
            }    
            }

 

 

해당 TEST 코드를 실행하면 AppConfig.class에 등록 되어 있는 bean들의 메타 정보를 확인할 수 있습니다.

728x90
반응형
728x90

🎈 스프링 빈 조회 - 동일한 타입이 둘 이상

타입으로 조회 시 같은 타입의 스프링 빈이 둘 이상이면 오류가 발생합니다.빈 이름을 지정해줌으로써 오류를 방지할 수 있습니다. 스프링 빈의 타입을 조회하는 문법은 ac.getBeansOfType();을 사용하면 해당 타입의 모든 빈을 조회할 수 있습니다.

👉 동일한 타입의 스프링 Bean의 DI 컨테이너에서 Bean을 조회했을 때의 예시

        public class ApplicationContextSameBeanFindTest {

            AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SameBeanConfig.class);

            @Test
            @DisplayName("타입으로 조회 시 같은 타입이 둘 이상 있으면, 중복 오류가 발생합니다.")
            void findBeanByTypeDuplicate() {

                //DiscountPolicy bean = ac.getBean(DiscountPolicy.class);
                //아래와 같이 코드를 작성한다면 예외가 발생하게 되는데, 지금 @Bean(하단에 작성한) 조회는 하단의 Bean을 조회
                /*MemberRepository bean = ac.getBean(MemberRepository.class);*/
                assertThrows(NoUniqueBeanDefinitionException.class, () -> ac.getBean(MemberRepository.class));

            }

            @Configuration
            //static를 사용 시 지역(scope)설정을 하기 위해 해당 클래스 안에서만 쓰겠다는 의미로 작성
            static class SameBeanConfig {

                //Bean의 이름은 다르나 반환되는 리턴값의 객체는 동일할 수 있음
                @Bean
                public MemberRepository memberRepository1() {
                    return new MemoryMemberRepository();
                }

                @Bean
                public MemberRepository memberRepository2() {
                    return new MemoryMemberRepository();
                }

            }

        }

해당 TEST 코드를 실행하게 되면 NoUniqueBeanDefinitionException 예외가 발생하게 됩니다.

 

먼저 두 개의 @Bean 메서드가 동일한 타입인 경우는 메서드의 반환 타입을 기반으로 알 수 있습니다. 위의 코드에서 memberRepository1()과 memberRepository2() 메서드는 모두 MemoryMemberRepository 타입의 객체를 반환하고 있으므로, 이 두 @Bean 메서드의 타입은 MemoryMemberRepository입니다.

 

스프링 프레임워크는 @Bean 메서드의 반환 타입을 기준으로 해당 메서드가 생성하는 빈(Bean)의 타입을 결정합니다. 따라서 같은 타입의 빈을 여러 개 정의하려면, 메서드의 반환 타입을 동일한 타입으로 지정해야 합니다.

이러한 상황에서 스프링은 빈의 식별을 위해 메서드 이름을 사용합니다. 즉, memberRepository1()과 memberRepository2() 메서드는 동일한 타입의 빈을 생성하지만, 스프링은 메서드 이름으로 구분하여 각각의 빈을 생성합니다. 따라서, 위의 코드에서 memberRepository1()과 memberRepository2() 메서드는 동일한 타입인 MemoryMemberRepository를 반환하지만, 스프링은 메서드 이름에 따라 각각의 빈을 구분합니다.


 

💡 스프링 빈 조회 - 상속관계

스프링 빈 상속 관계스프링 프레임워크에서 빈(Bean) 설정 시 사용되는 개념입니다.

부모타입으로 조회하면 본인을 포함하여 자식 타입도 함께 조회됩니다. 그래서 모든 자바 객체의 최상위 Object 타입으로 조회하면 모든 스프링 빈을 조회합니다. 스프링에서는 빈을 정의하고 구성하기 위해 XML 또는 어노테이션 기반의 설정을 사용합니다.

빈 상속은 한 빈을 기반으로 다른 빈을 정의하는 것을 의미합니다. 기본 빈(부모 빈)을 정의하고, 이를 상속하는 빈(자식 빈)을 생성할 수 있습니다. 자식 빈은 부모 빈의 설정을 상속하면서 추가적인 설정이나 오버라이딩을 할 수 있습니다.

빈 상속을 사용하면 다음과 같은 장점을 얻을 수 있습니다:

  1. 코드 중복 감소: 공통적인 설정을 부모 빈에서 정의하고, 자식 빈에서 추가적인 설정만 정의하여 중복을 줄일 수 있습니다.
  2. 유지보수성 향상: 공통된 설정을 부모 빈에서 관리하므로, 변경이 필요한 경우 부모 빈만 수정하면 모든 자식 빈에 반영됩니다.
  3. 가독성과 일관성: 빈 상속을 통해 관련 빈들을 계층 구조로 표현할 수 있으며, 코드의 가독성과 일관성을 높일 수 있습니다.

빈 상속은 스프링의 설정 파일(XML 또는 어노테이션)에서 parent 속성을 사용하여 부모 빈을 지정하는 방식으로 사용됩니다. 자식 빈은 부모 빈의 설정을 상속하며, 필요한 경우 추가적인 설정을 오버라이딩하여 사용할 수 있습니다.

 



        public class ApplicationContextExtendsFindTest {

            AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);

            @Test
            @DisplayName("부모 타입으로 조회 시 자식이 둘 이상 있으면 중복 오류가 발생합니다.")
            void findBeanByParentTypeDuplicate() {
            
                //아래와 같이 작성한 후 bean을 조회한다면 현재 2개의 @Bean이 생성되어 있어 두개 다 조회되며
                //NoUniqueBeanDefinitionException 예외가 발생
                //DiscountPolicy bean = ac.getBean(DiscountPolicy.class);
                assertThrows(NoUniqueBeanDefinitionException.class, () -> ac.getBean(DiscountPolicy.class));
            
			}

            @Test
            @DisplayName("부모 타입으로 조회 시 자식이 둘 이상 있으면 빈 이름을 지정하면 됩니다.")
            void findBeanByParentTypeBeanName() {
                DiscountPolicy ratediscountPolicy = ac.getBean("rateDiscountPolicy", DiscountPolicy.class);
                assertThat(ratediscountPolicy).isInstanceOf(RateDiscountPolicy.class);
            }


            @Test
            @DisplayName("특정 하위 타입으로 조회하면 됩니다.(좋지 않은 방법)")
            void findBeanBySubType() {
                RateDiscountPolicy bean = ac.getBean(RateDiscountPolicy.class);
                assertThat(bean).isInstanceOf(RateDiscountPolicy.class);
            }


            @Test
            @DisplayName("부모 타입으로 모두 조회하기")
            void findAllBeanByParentType() {
                Map<String, DiscountPolicy> beansOfType = ac.getBeansOfType(DiscountPolicy.class);
                assertThat(beansOfType.size()).isEqualTo(2);

                for (String key : beansOfType.keySet()) {
                    System.out.println("key = " + key +" " + "value = " + beansOfType.get(key)  );

                }
            }

            @Test
            @DisplayName("부모 타입으로 모두 조회하기 - Object")
            void findAllBeanByObjectType() {
             Map<String, Object> beansOfType = ac.getBeansOfType(Object.class);
                for (String key : beansOfType.keySet()) {
                    System.out.println("key = " + key + "value = " + beansOfType.get(key));

                }

            }


            @Configuration
            static class TestConfig {

                @Bean
                public DiscountPolicy rateDiscountPolicy() {
                    return new RateDiscountPolicy();
                }

                @Bean
                public DiscountPolicy fixDiscountPolicy() {
                    return new FixDiscountPolicy();
                }
            }
        }
728x90
반응형
728x90

@Configuration & @Bean

@Configuration과 @Bean은 Spring Framework에서 사용되는 애노테이션입니다. 이들은 Spring IoC 컨테이너에게 빈(Bean) 객체의 생성 및 구성 방법을 알려주는 역할을 합니다.

@Configuration 애노테이션은 클래스를 Spring의 설정 클래스로 지정합니다. 설정 클래스는 Spring IoC 컨테이너에게 해당 애플리케이션의 구성 정보를 제공하는 역할을 합니다. @Configuration 애노테이션이 적용된 클래스는 하나 이상의 빈을 생성하기 위해 @Bean 애노테이션과 함께 사용됩니다.

@Bean 애노테이션은 메서드를 통해 Spring IoC 컨테이너에게 빈 객체의 생성 및 구성 방법을 알려줍니다. @Bean 애노테이션이 적용된 메서드는 빈 객체를 반환하며, 이러한 빈 객체는 Spring IoC 컨테이너에 의해 관리됩니다. @Bean 애노테이션이 적용된 메서드의 이름은 빈의 이름이 되고, 반환되는 객체는 해당 빈의 인스턴스가 됩니다.

예를 들어, @Configuration과 @Bean을 사용하여 빈을 정의하는 예시입니다

            @Configuration
            public class AppConfig {

            @Bean
            public MyBean myBean() {
                return new MyBean();
            }

            @Bean
            public AnotherBean anotherBean() {
                return new AnotherBean(myBean());
            }

            // 다른 빈 정의 메서드...
            }

위의 예시에서 AppConfig 클래스는 @Configuration 애노테이션이 적용되었으며, myBean()과 anotherBean() 메서드는 @Bean 애노테이션이 적용되어 각각 MyBean과 AnotherBean 빈을 정의합니다. anotherBean() 메서드에서는 myBean()을 참조하여 AnotherBean의 의존성 주입을 수행합니다.

이렇게 @Configuration과 @Bean을 함께 사용하여 설정 클래스에서 빈을 정의하면 Spring IoC 컨테이너가 해당 빈을 인스턴스화하고 관리합니다. 이를 통해 의존성 주입, 빈의 스코프 지정, 초기화 메서드 등을 구성할 수 있습니다.


ApplicationContext

ApplicationContextSpring Framework에서 IoC 컨테이너의 인터페이스입니다. IoC 컨테이너는 애플리케이션의 구성 정보를 로드하고, 빈 객체의 생성과 관리, 의존성 주입 등을 수행합니다. ApplicationContext 인터페이스는 이러한 기능을 정의하고 제공합니다.

빈(Bean) 관리: ApplicationContext는 설정된 빈 정의를 바탕으로 빈 객체의 생성과 관리를 수행니다. 빈 객체는 @Bean 애노테이션이나 XML 설정 파일에서 정의될 수 있습니다. ApplicationContext는 빈의 스코프(싱글톤, 프로토타입 등) 및 라이프사이클 콜백(초기화, 소멸 등)을 관리합니다.

ApplicationContext 인터페이스를 사용하여 기존 자바코드로 작성하여 DI 컨테이너에 담아두었던 빈(Bean) 정의 메서드들을 ApplicationContext에 등록하여 applicationContext.getBean(”Bean의 이름”, Bean의 타입) 해당 문법을 사용하여 선택한 Bean을 호출할 수 있습니다.
→ ApplicationContext ac = new AnnotationConfigApplicationContext(설정 클래스);
ex) ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
⇒ applicationContext.getBean("memberService", MemberService.class);

 

   
   		public class MemberApp {

            public static void main(String[] args) {

        //      AppConfig appConfig = new AppConfig();
        //      MemberService memberService = appConfig.memberService();


                //AppConfig 클래스를 설정으로 사용하여, AnnotationConfigApplicationContext를 생성하고,
                //애플리케이션에서 필요한 빈을 applicationContext를 통해 가져와서 사용이 가능합니다.
                ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
                MemberService memberService = applicationContext.getBean("memberService", MemberService.class);



                Member member = new Member(1L, "memberA", Grade.VIP);
                memberService.join(member);


                Member findMember = memberService.findMember(1L);
                System.out.println("new member = " + member.getName());
                System.out.println("findMember = " + findMember.getName());
            }

 

위 코드와 같이 기존에 AppConfig 객체를 생성하여 AppConfig에 생성된 객체들을 직접 호출하는 것이 아니라 applicationContext를 이용하여 Bean을 호출하였습니다. 해당 코드를 실행하게 되면 콘솔창에 DI 컨테이너에 @Bean으로 설정된 메서드들의 객체가 등록되어 출력되는 것을 확인할 수 있습니다. 출력시에는 해당 Bean의 이름(ex.memberServie).(도트연산자)를 이용하여 출력이 가능합니다.

ApplicationContext를 스프링 컨테이너라고 합니다. 기존에는 개발자가 AppConfig를 사용해서 직접 객체를 생성하고 DI를 했지만, 이제부터는 스프링 컨테이너를 통해서 사용합니다. 스프링 컨테이너는 @Configuration이 붙은 AppConfig를 설정(구성)정보로 사용하고, 여기서 @Bean이라고 적힌 메서드를 모두 호출해서 반환된 객체를 스프링 컨테이너에 등록합니다. 이렇게 스프링 컨테이너에 등록된 객체를 스프링 빈 (Spring Bean)이라고 합니다.

Spring Bean은 Spring Framework에서 관리되는 객체를 의미합니다. 의존성 주입(Dependency Injection, DI)객체 간의 의존 관계를 개발자가 직접 관리하지 않고, 외부에서 의존하는 객체를 주입하여 사용하는 디자인 패턴입니다. DI는 객체 간의 결합도를 낮추고 유연하고 재사용 가능한 코드를 작성할 수 있도록 도와줍니다. Spring Framework은 의존성 주입(Dependency Injection)을 통해 객체들을 생성, 관리, 연결하는데, 이러한 객체들을 “Spring Bean”이라고 합니다.

tmvmfld qlsdms @Bean이 붙은 메서드 명을 스프링 빈의 이름으로 사용합니다. 이전에는 개발자가 필요한 객체를 AppConfig를 사용해서 직접 조회했지만, 이제부터는 스프링 컨테이너를 통해서 필요한 스프링 빈(객체)를 찾아야 합니다. applicationContext.getBean() 메서드를 사용해서 찾을 수 있습니다.


💡 스프링 컨테이너와 빈

스프링 컨테이너를 부를 때, “BeanFactory”와 “ApplicationContext”로 구분해서 이야기합니다.

  • 스프링 컨테이너의 생성과정
    • new AnnotationConfigApplicationContext(AppConfig.class); 를 선언함과 동시에 스프링 컨테이너가 생성됩니다. 컨테이너가 생성되면서, 빈 이름(key), 빈 객체(value)로 만들어지는 스프링 빈 저장소가 만들어집니다. 스프링 컨테이너가 생성될 때는 구성 정보를 지정해주어야 하는데 여기선 AppConfig.class로 지정되었습니다. @Bean의 이름은 직접 부여할 수도 있습니다. ex) @Bean(name=”NewBeanName”)

    • 스프링 빈 등록 AppConfig.class가 구성정보로 등록 되어있기 때문에 해당 구성 정보에서 @Bean이 붙은 메서드들을 전부 호출해줍니다 .이 때 메서드의 이름은 Bean 이름으로 해당 메서드가 반환하는 값(객체)는 Bean 객체로 등록을 해줍니다. 빈 이름은 항상 다른이름을 부여해야 합니다. 같은 이름을 부여하면, 다른 빈이 무시되거나, 기존 빈을 덮어버리는 등의 설정에 따라 오류가 발생합니다.
    1. 스프링 설정 파일 작성: 먼저, 스프링 컨테이너를 생성하기 위해 스프링 설정 파일을 작성합니다. 스프링 설정 파일은 일반적으로 XML, Java Config 또는 어노테이션 기반으로 작성될 수 있습니다. 설정 파일에는 애플리케이션의 구성 정보와 빈(Bean) 정의, 의존성 설정 등이 포함됩니다.

    2. 스프링 컨테이너 생성: 설정 파일을 기반으로 스프링 컨테이너를 생성합니다. 스프링은 다양한 종류의 컨테이너를 제공합니다. 가장 일반적인 컨테이너는 ApplicationContext 인터페이스를 구현한 클래스들입니다. ApplicationContext의 구현체는 설정 파일의 종류에 따라 다르게 선택할 수 있습니다.
    • XML 기반 설정: ClassPathXmlApplicationContext, FileSystemXmlApplicationContext, XmlWebApplicationContext 등이 사용됩니다.
    • Java Config 기반 설정: AnnotationConfigApplicationContext, GenericApplicationContext 등이 사용됩니다.
    • Spring Boot: Spring Boot에서는 SpringApplication 클래스의 run() 메서드를 사용하여 자동으로 스프링 컨테이너를 생성합니다.
    1. 빈 생성과 의존성 주입: 스프링 컨테이너는 설정 파일을 기반으로 빈 객체를 생성하고 관리합니다. 설정 파일에 정의된 빈들은 컨테이너에 의해 인스턴스화되고, 의존성 주입(Dependency Injection)을 통해 서로간의 의존성이 해결됩니다. 빈 객체는 컨테이너에 의해 스코프(싱글톤, 프로토타입 등)에 따라 관리되며, 필요한 경우 라이프사이클 콜백을 수행합니다.

    2. 컨테이너 사용: 스프링 컨테이너가 생성되고 빈이 초기화되면, 개발자는 컨테이너에서 필요한 빈을 가져와 사용할 수 있습니다. 컨테이너는 빈들을 관리하고, 필요에 따라 의존성 주입을 수행하여 객체 간의 결합도를 낮추고 유연한 애플리케이션을 개발할 수 있도록 도와줍니다.

스프링 빈은 생성하고, 의존관계를 주입하는 단계가 나누어져 있습니다. 자바코드로 스프링 빈을 등록하면 생성자를 호출하면서 의존관계 주입도 한번에 처리가 됩니다.

👉 빈 이름 조회하기

        class ApplicationContextInfoTest {

            AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);

            @Test
            @DisplayName("모든 Bean 출력하기")
            void findAllBean() {
                String[] beanDefinitionNames = ac.getBeanDefinitionNames();
                for (String beanDefinitionName : beanDefinitionNames) {
                    Object bean = ac.getBean(beanDefinitionName);
                    System.out.println("bean = " + bean);
                }
            }


            @Test
            @DisplayName("애플리케이션 Bean 출력하기")        
            void findApplicationBean() {
                String [] beanDefinitionNames = ac.getBeanDefinitionNames();
                for (String beanDefinitionName : beanDefinitionNames) {
                    BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName);


                    //Role ROLE_APPLICATION : 직접 등록한 애플리케이션 빈
                    //Role ROLE_INFRASTRUCTURE : 스프링이 내부에서 사용하는 빈
                    if (beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION) {
                        Object bean = ac.getBean(beanDefinitionName);
                        System.out.println("beanDefinitionName  = " + beanDefinitionName + "object" + bean);
                    }

                }
            }
        }

 

빈에 대한 정보 출력하기 위해서 사용 가능한 메서드들이 있습니다. 실행하면 스프링에 등록된 모든 빈 정보를 출력할 수 있습니다.
모든 빈 출력하기 getBeanDefinitionNames() : 스프링에 등록된 모든 빈 이름을 조회
getBean() : 빈 이름으로 빈 객체(인스턴스)를 조회
애플리케이션 빈 출력하기 스프링이 내부에서 사용하는 빈은 제외하고, 내가 등록한 빈만 출력 가능 / 스프링이 내부에서 사용하는 빈은 getRole()로 구분이 가능합니다.
ROLE_APPLICATION :
일반적으로 사용자가 정의한 빈 ROLE_INFRASTRUCTURE : 스프링이 내부에서 사용하는 빈

728x90
반응형

+ Recent posts