- 브라우저는 HTML 파일 응답을 받고 나서, HTML안에 있는 CSS, 자바스크립트, 이미지 등의 콘텐츠들을 전부 받아 올 때까지 호출함 - 콘텐츠의 수가 많을수록 로딩 완료 시간은 길어짐 - 성능을 위해서 HTTP 요청 수를 줄여야 함
HTTP 요청을 줄이는 방법
스크립트 파일 병합
- 자바스크립트나 CSS파일들을 기능별로 분리하여 각각의 파일로 저장하고 호출하는 방식을 사용하는데, 이 방법은 HTTP 요청 수를 증가시키는 요인임 - 모듈화 된 여러 파일들을 하나로 합치고, 이 하나의 파일을 브라우저가 실행하는 것이 여러 개의 파일들을 각각 호출하는 것과 동일한 결과를 만들 수 있다면 파일 병합으로 HTTP 요청 수를 줄일 수 있음 - 병합한 파일의 크기가 너무 크다면 그 파일을 로딩하는 시간이 너무 길어질 수 있어서 적절한 크기를 유지해야 함
인라인 이미지
CSS 스프라이트
- 여러 개의 이미지를 하나의 이미지 파일로 결합해서 필요한 이미지가 위치하는 픽셀 좌표 정보를 사용
콘텐츠 파일 크기 줄이기
스크립트 파일 압축 전달
- 웹 서버가 지원하는 방식으로 스크립트를 압축해 클라이언트에게 더 작은 크기로 전달하고, 클라이언트가 압축을 해제하여 원본 콘텐츠를 이용 - 서버(Content-Encoding)와 클라이언트(Accept-Encoding)가 서로가 지원하는 압축방식 중 하나를 골라서 정해야 함 - Accept-Encoding : 클라이언트가 자신이 지원하는 압축 알고리즘을 서버에 알려줌 - Content-Encoding : 서버는 클라이언트가 알려준 압축 알고리즘 중 서버가 지원하는 알고리즘 하나를 선택해서 클라이언트에게 알려줌
- 스크립트 파일에 포함된 주석, 공백, 개행 문자들처럼 실제 로직에는 아무 영향을 주지 않는 부분들을 제거하여 파일의 크기를 줄이는 방법 - 개발서버에는 원본을 두고, 운영서버에는 스크립트를 최소화하여 배포 - 더 이상 변경이 일어나지 않은 것이라 예상되는 스크립트를 대상으로 최소화 - Minify
- 각 브라우저에 특화된 이미지를 사용 - WebP : 손실 압축, 구글 개발, 이미지 전달 용도로 많이 사용하던 jpeg를 대체하기 위해 웹 사이트 트래픽 감소, 로딩 속도 단축을 목적으로 개발 - JPEG XR : 마이크로소프트 - 크롬에는 WebP, IE에는 JPEG XR제공 / JPEG만 들고 있다가 요청 브라우저에 따라 각각으로 변환하여 전달
- PNG는 알파채널로 투명 기능을 사용하는데, 투명 기능이 필요 없으면 사이즈가 작은 JPEG를 사용
래스터 이미지 vs 벡터 이미지
- 레스터 이미지는 사각형 필셀에 색상 데이터를 입력해서 표현, 큰 사이즈와 고품질 이미지를 위해서는 그만큼 픽셀을 추가해야 하므로, 용량이 커짐
- 벡터 이미지는 수학적인 메타 정보를 가지고 있어서 화면 스케일에 관계없이 항상 선명한 이미지를 제공, 이미지의 그림이 복잡해지면, 이를 위한 정보가 계속 늘어나게 됨, SVG는 텍스트 기반이라 svgz로 만들어서 사용 가능
무손실 압축 vs 손실압축
- 무손실 압축은 각 이미지 유형들을 서로 다르게 처리해야 함, 대부분 라이브러리 스크립트로 압축 자동화가 가능
- PNG : PNG는 청크 형태로 저장, 이미지 표현 정보인 핵심 청크가 아닌 정보성 청크는 삭제해도 됨(Pngcrush, Pngquant, PngOptimizer)
- JPEG : 이미지 외에 많은 메타 데이터가 있음(주석, 공백, 편집 앱 정보, 카메라 정보, 촬영 날짜/위치 등) 제거해도 이미지 품질 손실 없음(MozJPEG, libJpeg, Guetzli)
- 손실 압축은 이미지의 정보를 누락, 사용자의 시각적 경험을 손실시키지 않으면서 이미지를 손실시키면 됨
- 사람이 품질 저하를 모르게 하면서 파일 크기를 줄일 수 있는 JPEG품질은 100~75% => 85~80% 권장(시각적 손실이 거의 없고, 사이즈는 70% 이하로 축소 가능)
반응형 웹과 이미지
문제점 : (아직) 필요하지 않은 리소스들도 과도하게 다운로드
- 반응형 웹에서는 이미지를 내려받아서 화면의 사이즈에 맞게 줄이는 유동형 이미지 방법을 사용, 이 방법에서 화면상의 이미지가 작아졌다고 실제 이미지의 크기가 작아진 것은 아니고, 브라우저에서 큰 이미지를 받아다가 축소하는 처리하는 추가 과정이 이루어지게 되는 낭비가 발생함
- PC와 모바일에서는 같은 화면이어도 화면에 나타나는 이미지의 개수가 다름(PC가 훨씬 많음), 모바일 화면을 구성하기 위해 미디어 쿼리에 display: none;을 줘서 숨기는데, 이렇다고 해서 이미지를 다운로드하지 않는 것은 아님
- 모바일 화면을 볼 때 아직 스크롤을 내려서 확인하지 않은 화면에 있는 이미지들도 전부 다운로드
해결방법 : 사용자의 환경에 따라 그 환경에 적합한 이미지를 제공
- 사용자의 화면 크기에 따라 여러 가지 사이즈(버전)의 이미지를 준비해서 알맞게 전송
- FE : 미디어 쿼리로 사용자의 환경을 파악하고 그 환경에 맞는 이미지를 호출, 과도하게 사용하는 경우 FE코드가 무거워짐
- BE : 서버에서 사용자의 환경에 맞는 이미지를 전송, 서버에서 사용자의 환경을 파악하기 위한 작업이 필요함, Client Hint
- Client Hint : 사용자의 환경은 서버에 전달하기 위함, HTTP 헤더에 넣어서 전송
- 지연 로딩 : 당장 화면에 보여주지 않은 리소스를 처음부터 전부 다운로드하는 것은 낭비임, 실제로 필요하거나 스크롤을 내려서 화면이 움직일 때 이미지를 다운받음(lazyload라이브러리)
- 모바일 우선 접근 : 웹을 구현할 때 모바일 화면부터 개발, PC 화면부터 구성하면 모바일 화면을 구성할 때 PC에서 사용한 것들을 모바일에 그대로 사용하는 일이 많음
큰 파일은 작게 나누어서 전송
- 크기를 알 수 없거나 대용량 파일의 일부분을 순서대로 다운로드하는 부분 요청 응답 방식 - Range - Content-Range - 클라이언트는 구간별로 부분 파일 요청을 반복해서, 콘텐츠가 끊김 없이 서비스될 수 있도록 함
ex. Range: bytes=0~1023 Content-Range: bytes 0~1023 / 10000 => 전체 10000중에서 1001~2000만 전송 Content-Length: 1024
브라우저 렌더링 최적화
- DOM최적화 : 웹 페이지에 구문 오류가 많을수록 예외 처리를 위한 메모리, CPU를 소모하므로, 구문 오류 최소화하기, 과도하게 HTML 태그를 중첩시키지 않기 (15단계 넘지 않도록) - JS와 CSS배치 : HTML구문분석은 JS를 만나면 JS를 처리할 때까지 DOM생성 작업 중단, JS는 CSSOM생성이 완료될 때까지 JS처리 중단 => JS와 CSS는 랜더링을 지연할 수 있는 요소 => CSS는 최대한 위쪽에 두어서 CSSOM을 빠르게 만들도록, JS는 최대한 하단에 두어서 DOM, CSSOM생성 완료 후에 JS 수행되도록 - JS최적화 : 타사 솔루션 JS를 배치하는 경우도 있어서 하단에 배치하기 어려운 경우도 많음, 꼭 필요한 JS그룹과 그렇지 않은 JS그룹을 구분해서 후자는 async, defer적용 (async : DOM생성과 동시에 JS처리 / defer : DOM생성 중 별도 스레드로 JS 다운로드한 후, 처리는 DOM생성 완료 후에) - CSS최적화 : 모든 CSS가 첫 화면 구성에 쓰이지는 않음, CSS를 적절히 분리해서 필요한 페이지에 필요한 CSS만 적용 - 이미지 로딩 최적화 : background-image, 지연 로딩, Progressive JPG
도메인 분할
- 여러 도메인을 소유한 경우 웹 콘텐츠를 병렬적으로 동시에 다운로드 - 브라우저 만다 허용하는 최대 동시 연결 개수를 제한함 (대부분 6개) - 사이트 전체의 쿠키 사이즈를 줄일 수 있음 - 각 도메인은 동일한 인증서를 사용해야 함, 인증서가 다르면 추가적인 TLS협상 과정이 생겨버림 - 적절한 도에인 숫자를 결정해야 함, 너무 많으면 도리어 CPU 과부하
캐시 최적화
HTTP캐시 목적 1) 원본 서버로의 요청 수를 최소화, 네트워크 왕복 수를 줄여서 사용자에 대한 응답 속도를 단축 2) 완전한 콘텐츠를 응답하지 않아도 됨, 네트워크 대역폭과 리소스 낭비를 줄임
- 캐시 저장소에 접근하는 시간이 원래 데이터 위치에 접근하는 시간보다 짧고, 시스템의 리소스를 아낄 수 있음 - 프록시 서버 : 인터넷상에서 주고받은 데이터를 캐시 하고 다음에 요청하는 사용자에게 이를 제공하는 방식을 사용하여, 사용자들은 좀 더 빠르게 콘텐츠를 받을 수 있음 - PUSH : 미리 캐시 서버에 데이터를 복사해둠 / PULL : 실제 요청이 있을 때 캐시에 저장 - 웹 브라우저에서도 이미지나, 자바스크립트, CSS파일 등을 사용자의 브라우저가 캐시 해서 저장함 - 브라우저 캐시에 대한 내용은 웹서버의 캐시 설정을 따름 => 참고 - Cache-Control 응답 헤더로 클라이언트에 캐시 설정 정보를 알려줌 (유효시간) - Expire는 만료 시점, Expire는 Date헤더를 함께 보내야 함, Date헤더에는 요청에 대한 응답이 작성된 시점을 표시 - Cache-Control, Expire둘다 있으면 Cache-Control를 우선 사용
Cache-Control: max-age=3600 => 브라우저 캐시 저장 후 3600초 동안 유효 Cache-Control: no-store => 캐시 하면 안 되는 콘텐츠 Cache-Control: no-cache => 원본 서버의 콘텐츠 갱신 여부를 확인하고 변경이 없는 경우만 캐시 된 콘텐츠 사용 Cache-Control: no-cache, no-store, must-revalidate : 캐시 절대 불가능 Expire: Mon, 30 Nov 2021 08:00:00 GMT => 2021년 11월 30일 08시에 만료
- 콘텐츠에 따라 캐싱을 어떻게 할 것인지 판단해야 함
- 동일한 파일을 서버의 여러 곳에 분산시키면 안 됨, 캐시 서버는 URL을 키로 하여 동작하기 때문에, 동일한 내용이 캐시에도 또 생김 - 캐시 오염 제거 : 원본은 한 개인데, 캐시에는 많음 => 쿼리 스트링 값이 달라도 응답이 매번 같다면, 캐시 키는 쿼리 스트링을 무시하도록, 쿼리 스트링의 순서를 동일하게
- 캐시 서버는 캐시 키를 사용
- 캐시 서버가 원본의 복사본을 저장하고 빠르게 조회하기 위해서 사용하는 키 값
- 일반적인 웹 캐시는 클라이언트가 요청하는 URL을 캐시 키로 사용함 (호스트/패스?쿼리스트링)
- 캐시 방지를 위해서 'version=오늘날짜' 와 같은 쿼리 스트링을 추가해서 요청할 수도 있음
EX) 아래 3개의 캐시 키는 서로 다른 객체를 참조함
www.abc.com/ui/css/common.css
www.abc.com/ui/css/common.css?vesion=20210721
www.abc.com/ui/css/common.css?vesion=20210722
CDN
- CDN은 웹 콘텐츠를 사용자에 세 빠르게 전달하기 위해 캐시 서버나 에지 서버와 같은 대용량 인터넷 캐시 영역에 콘텐츠들을 저장해서 사용하는 방식 - 클라이언트와 서버 사이에 발생 가능한 네트워크 지연이나 패킷 손실이 줄어듦 - 사용자는 비교적 가까운 에지 서버에서 콘텐츠를 받기 때문에 RTT가 줄어들어 빠르게 받을 수 있음 - CDN 서버가 콘텐츠를 전달해 주므로, 원본 서버의 부하를 덜어 줄 수 있음 - 캐시 사용률이 높아짐 - 서버 유지보수 용이
- 스프링 MVC패턴에서 @Controller, @Service, @Repository 사용
- @Bean을 하나하나 만드는 것보다 @Component만 작성하는 것이 훨씬 간편
- OCP, DIP 준수 가능
수동 등록을 사용하면 좋은 경우
- 애플리케이션의 컨트롤러, 서비스, 리포지토리와 같은 업무 로직은 자동 등록을 사용
1) 업무 로직 외에 DB커넥션, Transaction설정, 공통 Log, 외부 라이브러리 연동과 같은 기술 지원 빈들은 개수가 적기 때문에 수동 등록으로 하여 업무 로직과 분리하여 관리하면 좋음
2) 의존관계 주입 중 조회한 빈이 모두 필요한 경우 List, Map을 사용하는데, 이에 해당하는 빈들은 하나에 모아서 @Bean으로 수동 등록하면 한 번에 보고 이해가 쉬움 (or 동일 패키지에 두고 자동 등록)
@Configuration
public class AppConfig {
@Bean
public DiscountPolicy rateDiscountPolicy(){
return new RateDiscountPolicy();
}
@Bean
public DiscountPolicy fixDiscountPolicy(){
return new FixDiscountPolicy()
}
}
DiscountPolicy라는 동일 타입의 빈인 RateDiscountPolicy, FixDiscountPolicy를 모두 가져와 동적으로 사용하고 싶은 경우는 어떻게 할까?
=> List, Map에 빈을 담아와서 필요할 때 꺼내서 사용
public class AllBeanTest {
@Test
public void findAllBean() {
ApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class, DiscountService.class);
DiscountService discountService = ac.getBean(DiscountService.class);
Member member = new Member(1, "memberA", Grade.VIP);
int discountPrice = discountService.discount(member, 25000, "rateDiscountPolicy");
Assertions.assertThat(discountPrice).isEqualTo(2500);
}
static class DiscountService {
// 여러개의 빈이 모두 필요
private final Map<String, DiscountPolicy> dcPolicyMap;
private final List<DiscountPolicy> dcPolicyList;
public DiscountService(Map<String, DiscountPolicy> dcPolicyMap, List<DiscountPolicy> dcPolicyList) {
this.dcPolicyMap = dcPolicyMap;
this.dcPolicyList = dcPolicyList;
}
public int discount(Member member, int price, String discountCode) {
DiscountPolicy discountPolicy = dcPolicyMap.get(discountCode);
return discountPolicy.discount(member, price);
}
}
}
NoUniqueBeanDefinitionException: No qualifying bean of type 'com.example.discount.DiscountPolicy' available: expected single matching bean but found 2: fixDiscountPolicy,rateDiscountPolicy
@Component
public class FixDiscountPolicy implements DiscountPolicy{
......
}
@Component
public class RateDiscountPolicy implements DiscountPolicy{
......
}
=> DiscountPolicy라는 Type으로 찾은 빈이 2개라 오류 발생함
찾은 빈이 2개 이상인 경우 어떤 빈을 사용?
1. @AutoWired 필드 이름, 생성자 파라미터 이름 매칭
생성자 파라미터 Type으로 찾음 => 2개 이상 => 필드 이름, 생성자 파라미터 이름으로 찾음
private final DiscountPolicy rateDiscountPolicy;
2. @Qualifier
- 추가로 구분할 수 있는 수단 제공
@Component
@Qualifier("mainDiscountPolicy")
public class RateDiscountPolicy implements DiscountPolicy{
......
}
@Component
@Qualifier("fixDiscountPolicy")
public class FixDiscountPolicy implements DiscountPolicy{
......
}
@Component
public class OrderServiceImpl implements OrderService{
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
@Autowired
public OrderServiceImpl(MemberRepository memberRepository,
@Qualifier("mainDiscountPolicy") DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
/*
비즈니스 로직
*/
}
3. @Primary
- 우선순위를 지정
- @AutoWired가 여러개의 빈을 찾게 되면 @Primary가 붙은 빈을 우선적으로 사용
@Component
@Primary
public class RateDiscountPolicy implements DiscountPolicy{
......
}
@Component
public class FixDiscountPolicy implements DiscountPolicy{
......
}
@Component
public class OrderServiceImpl implements OrderService{
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
/*
비즈니스 로직
*/
}
2. 생성자가 1개니깐 @AutoWired생략 가능
@Component
public class OrderServiceImpl implements OrderService{
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
/*
비즈니스 로직
*/
}
3. Lombok적용
- Lombok의 @RequiredArgsConstructor를 사용하면 final이 붙은 필드들에 대한 생성자 코드를 자동으로 생성함
- 생성자 코드를 작성할 필요 없음
- final키워드 필드 + @AutoWired +생성자 1개 => @RequiredArgsConstructor
@Component
@RequiredArgsConstructor
public class OrderServiceImpl implements OrderService{
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
/*
비즈니스 로직
*/
}
@Component
@RequiredArgsConstructor
public class MemberServiceImpl implements MemberService{
private final MemberRepository memberRepository;
/*
비즈니스 로직
*/
}
- AppConfig처럼 @Bean을 사용하여 직접 코드를 작성하고 의존 관계도 작성해서 스프링 빈으로 등록할 수 있었음
- 이렇게 스프링 빈을 작성하면, 등록할 빈의 개수가 많아지면 하나하나 작성해줘야 하고, 관리도 어려울 수 있음
=> 스프링에서는 Java설정 파일이나 XML 없이도 스프링 빈을 등록할 것들을 찾아서 등록하는 컴포넌트 스캔이라는 기능을 제공하고, @AutoWired를 사용하여 의존관계 주입도 자동으로 해줌
AppConfig
@Configuration
public class AppConfig {
@Bean
public MemberService memberServiceImpl() {
return new MemberServiceImpl(memberRepository());
}
@Bean
public OrderService orderServiceImpl() {
return new OrderServiceImpl(new MemoryMemberRepository(), new RateDiscountPolicy());
}
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
@Bean
public DiscountPolicy discountPolicy(){
return new RateDiscountPolicy();
//return new FixDiscountPolicy()
}
}
AutoAppConfig
- @ComponentScan사용
- @Bean사용 X
@Configuration
@ComponentScan(
excludeFilters = @ComponentScan.Filter(type= FilterType.ANNOTATION, classes = Configuration.class)
)
public class AutoAppConfig {
}
@ComponentScan
- @ComponentScan을 붙여서 컴포넌트 스캔을 사용하면 @Componet가 붙은 클래스를 스캔하여 스프링 빈으로 등록
- 스프링 빈 등록시 빈 이름은 첫글자가 소문자인 클래스명을 사용 (@Bean은 메서드 명을 빈 이름으로)
@Component
public class MemberServiceImpl implements MemberService{
......
}
@Component
public class OrderServiceImpl implements OrderService{
......
}
@Component
public class RateDiscountPolicy implements DiscountPolicy{
......
}
@Component
public class MemoryMemberRepository implements MemberRepository{
......
}
- 생성자에 @AutoWired를 붙이면 자동으로 의존관계를 주입해줌
- 생성자 파라미터의 Type으로 스프링 컨테이너에서 스프링 빈을 찾아서 주입함
@Component
public class OrderServiceImpl implements OrderService{
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
......
}
1) 설정클래스에 @ComponentScan이 붙음
2) @Component가 붙은 클래스들을 클래스 명으로 스프링 반 등록함
3) @AutoWired + 생성자를 사용하여 의존관계를 자동으로 주입함 (생성자의 Type으로 빈 찾음)
=> @ComponentScan, @Component, @AutoWired는 항상 같이 사용됨
컴포넌트 스캔 범위
- 어플리케이션 구동 시 모든 클래스들을 스캔하려면 오래 걸리므로, 필요한 위치를 탐색하도록 지정
- @ComponetScan에 basepackage를 지정하지 않으면 @ComponetScan가 붙은 설정 클래스가 존재하는 패키지가 스캔 시작 위치 => 따로 basePackage를 지정하지 않고 설정 클래스를 프로젝트의 최상단에 두면 됨 => 하위 패키지까지 모두 스캔함
1) 자동등록으로 동일한 이름으로 빈을 등록하려 하는 경우 에러 발생 : ConflictingBeanDefinitionException
1-1) 동일한 이름은 아니지만 타입이 같은 빈을 두 개 등록하는 경우(FixDiscountPolicy, RateDiscountPolicy에 둘 다 @Component작성)
=> @AutoWired 의존관계 주입시 에러 발생 가능 (생성자 파라미터로 찾은 스프링 빈이 2개 이상...)
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'orderServiceImpl' defined in file [C:\Users\남궁준\Desktop\study\demo\out\production\classes\com\example\order\OrderServiceImpl.class]: Unsatisfied dependency expressed through constructor parameter 1; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.example.discount.DiscountPolicy' available: expected single matching bean but found 2: fixDiscountPolicy,rateDiscountPolicy
@Component
public class FixDiscountPolicy implements DiscountPolicy{
......
}
@Component
public class RateDiscountPolicy implements DiscountPolicy{
......
}
@Component
public class OrderServiceImpl implements OrderService{
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
......
}
2) 수동등록, 자동 등록으로 이름이 같은 빈을 등록하려 하는 경우 : 수동이 우선권(수동 빈이 자동 빈을 오버라이딩해버림) => 사용 X => 스프링 부트에서는 오류를 발생시킴(오버라이딩 막음)
@Component
public class MemoryMemberRepository implements MemberRepository{
......
}
@Configuration
@ComponentScan(
excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class) // AppConfig.class 제외
)
public class AutoAppConfig {
@Bean(name="memoryMemberRepository")
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
}
- 애플리케이션은 보통 다수의 클라이언트들이 요청을 보냄, 매 요청마다 서버에 객체를 생성해서 전달하는 것은 메모리 낭비가 심함
public class AppConfig {
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
public OrderService orderService() {
return new OrderServiceImpl(new MemoryMemberRepository(), new RateDiscountPolicy());
}
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
public DiscountPolicy discountPolicy(){
return new RateDiscountPolicy();
//return new FixDiscountPolicy()
}
}
public class MemberApp {
public static void main(String[] args) {
AppConfig appConfig = new AppConfig();
MemberService memberService = appConfig.memberService();
Member member = new Member(1, "memberA", Grade.VIP);
memberService.join(member);
Member findMember = memberService.findMember(1L);
System.out.println("findMember.getName() = " + findMember.getName());
System.out.println("findMember.getName() = " + findMember.getGrade());
}
}
- 위와 같이 스프링을 사용하지 않은 AppConfig는 클라이언트에서 매번 new를 통해 새로운 객체를 생성하게 됨
싱글톤 패턴
- 최초에 static영역에 객체를 1개만 생성
- 외부에서는 getInstance() 메서드를 통해서만 객체를 받아갈 수 있도록 만듦
- 외부에서 생성자 호출을 못하도록 private생성자를 만들어 둠
public Class SingleTonService() {
private static final SingleTonService instance = new SingleTonService();
public static SingleTonService getInstance() {
return instance;
}
private SingleTonService(){}
}
스프링 컨테이너는 스프링 빈을 싱글톤으로 관리함
- 스프링 컨테이너에서는 직접 코드로 싱글톤을 구현하지 않아도 알아서 Bean을 하나만 생성해서 싱글톤으로 관리함
- 스프링 컨테이너를 사용하면 웹 애플리케이션에서 직접 싱글톤 구현 없이 간편하게 객체들을 싱글톤으로 관리할 수 있음
public class SingleTonTest {
@Test
void SingleTonTest() {
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
MemberService memberService1 = ac.getBean("memberService", MemberService.class);
MemberService memberService2 = ac.getBean("memberService", MemberService.class);
System.out.println("memberService1 = " + memberService1);
System.out.println("memberService2 = " + memberService2);
Assertions.assertThat(memberService1).isSameAs(memberService2);
}
}