Open yepdi opened 2 years ago
이전에 본 오브젝트 책에서 다룬 내용이다 🍰
클라이언트에 영향을 주지 않고도 기능을 추가할 수 있는 것 (역할을 구분했기 때문)
유연하고 변경 용이하도록 하게 하는 것
협력이라는 객체 사이의 관계에서 시작하여 클라이언트를 변경하지 않고 서버의 구현 기능을 유연하게 변경할 수 있다
역할(인터페이스)와 구현(구현 객체, 인터페이스 구현 클래스)로 구분시 장점
스프링은 다형성을 극대화해서 이용할 수 있다.
스프링에서 이야기하는 제어의 역전, 의존관계 주입은 다형성을 활용해서 역할과 구현을 편리하게 다룰 수 있도록 지원
SRP (단일 책임 원칙) 하나의 클래스는 하나의 책임만 가져야한다 변경을 기준으로 파급 효과가 적은 것. 변경이 있을 때 하나의 클래스만 고치도록
OCP (개방-폐쇄의 원칙) 확장에는 열려 있으나 변경에는 닫혀있어야한다(다형성으로 구현) 인터페이스를 구현한 새로운 클래스를 만들어서 새로운 기능 구현 but 구현 객체를 변경하기 위해 클라이언트 코드를 변경해야 한다 => 객체를 생성하고 연관관계를 맺어주는 별도의 조립, 설정자가 필요
LSP (리스코프 치환 원칙) 프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야한다. https://github.com/yepdi/TIL/issues/18#issue-1308940960
ISP (인터페이스 분리 원칙) 특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 나음 인터페이스가 명확해지고 대체 가능성이 높아짐
DIP (의존관계 역전 원칙) 구현 클래스에 의존하지 않고 인터페이스(추상화)에 의존. 역할에 의존해야한다
MemberRepository memberRepository = new MemoryMemberRepository()
해당 코드는 인터페이스인 MemberRepository
와 MemoryMemberRepository
둘 다 의존하는 구조
OCP, DIP 위반하는 코드
객체 지향의 핵심은 다형성. 다형성 만으로는 쉽게 부품을 갈아 끼우듯이 개발할 수 없다. 다형성 만으로는 구현 객체를 변경할 때 클라이언트 코드도 함께 변경된다. 즉, 다형성 만으로는 OCP, DIP를 지킬 수 없다. 뭔가 더 필요하다
다형성 + OCP, DIP를 가능하도록 지원 DI : 의존관계, 의존성 주입. DI 컨테이너 제공 클라이언트 코드의 변경 없이 기능 확장
SRP (단일 책임 원칙)
DIP (의존관계 역전 원칙)
OCP(확장에는 열려 있으나 변경에는 닫혀 있다)
제어의 역전 IoC(Inversion of Control)
프레임워크 vs 라이브러리
정적인 클래스 의존관계
동적인 객체 인스턴스 의존 관계
IoC 컨테이너, DI 컨테이너 AppConfig 처럼 객체를 생성하고 관리하면서 의존관계를 연결해주는 것 IoC 컨테이너 또는 DI 컨테이너 라 한다 또는 어셈블러, 오브젝트 팩토리 등으로 불린다
스프링 컨테이너
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
ApplicationContext는 스프링 컨테이너 이자 인터페이스 스프링 컨테이너는 XML 기반, 애노테이션 기반 자바 설정 클래스도 만들 수 있다
AppConfig.class
)스프링은 빈을 생성하고 의존관계를 주입하는 단계가 나눠져있다. 그런데 이렇게 자바 코드로 스프링 빈을 등록하면 생성자를 호출하면서 의존관계 주입도 한번에 처리된다.
BeanFactory 기능을 모두 상속받아서 제공
애플리케이션을 개발할 때는 빈은 관리하고 조회하는 기능은 물론이고 수 많은 _부가 기능_이 필요 (빈의 관리기능 + 편리한 부가 기능 제공)
다양한 설정 형식 지원 스프링 컨테이너는 다양한 형식의 설정 정보를 받아드릴 수 있게 유연하게 설계 되어 있다
XML 설정: XML 기반 설정은 잘 사용하지 않는다. 레거시에서는 많이 사용.
AnnotationConfigApplicationContext
은 factoryBeanName과 factoryMethodName이 존재GenericXmlApplicationContext
은 class path resource에 정의된 .xml 파일을 통해서 읽어온다 클래스의 인스턴스가 딱 1개만 생성되는 것을 보장하는 것 객체 인스턴스를 2개 이상 생성하지 못하도록 막아야한다
class SingletonService {
companion object {
// 1. Static 영역에 객체 instance를 미리 하나 생성해서 올려둔다
private val instance = SingletonService()
// 2. 객체 인스턴스가 필요하면 getInstance() 메서드를 통해서만 조회할 수 있다. 같은 인스턴스 반환
fun getInstance(): SingletonService = instance
}
// 3. 딱 1개의 객체 인스턴스만 존재해야 하므로 생성자를 private으로 막아서 외부에 new 키워드로 객체 인스턴스가 생성되는 것을 막는다
private constructor()
fun logic() {
println("싱글톤 객체 로직 호출")
}
}
싱글톤 패턴을 적용하면 고객의 요청이 올 때마다 객체를 생성하는 것이 아니라 만들어진 객체를 공유해서 효율적으로 사용할 수 있다
스프링 컨테이너는 싱글톤 패턴의 문제점을 해결하면서 객체 인스턴스를 싱글톤으로 관리
객체 인스턴스를 하나만 생성해서 공유하는 싱글톤 방식은 여러 클라이언트가 하나의 같은 객체 인스턴스를 공유하기 때문에 싱글톤 객체는 상태를 유지하게 설계하면 안된다 즉, 무상태(stateless)로 설계해야 한다
스프링 빈의 필드에 공유 값을 설정하면 정말 큰 장애가 발생할 수 있다 공유 필드는 조심해야한다. 스프링 빈은 항상 무상태(stateless)로 설계하자 🤭
val ac = AnnotationConfigApplicationContext(AppConfig::class.java)
val bean = ac.getBean(AppConfig::class.java)
println("bean = ${bean.javaClass}")
// bean = class core.studyspring.AppConfig$$EnhancerBySpringCGLIB$$4c769945
-> AppConfig는 @Configuration이 적용된 상태이다. 만약 @Configuration이 없다면 bean은 class core.studyspring.AppConfig 로 출력될 것
클래스 명에 xxxCGLIB가 붙으면서 상당히 복잡해진 것을 알 수 있다. 스프링이 CGLIB라는 바이트 코드 조작 라이브러리를 사용해서 AppConfig 클래스를 상속받은 임의의 다른 클래스를 만들고 그 다른 클래스를 스프링 빈으로 등록
@Bean
fun memberRepository(): MemberRepository {
if (memoryMemberRepository가 이미 스프링 컨테이너에 등록되어 있다면?) {
return 스프링 컨테이너에서 찾아서 반환
} else {
기존 로직을 호출해서 MemoryMemberRepository를 생성하고 스프링 컨테이너에 등록
return 반환
}
}
@Bean만 사용해도 스프링 빈으로 등록되지만, 싱글톤을 보장하지 않는다 스프링 정보는 항상 @Configuration 을 사용하자
@Configuration
@ComponentScan(
basePackages = ["core.studyspring"],
excludeFilters = [ComponentScan.Filter(type = FilterType.ANNOTATION, classes = arrayOf(Configuration::class))]
// 테스트용
)
class AutoAppConfig {
}
@ComponentScan @ComponentScan은 @Component가 붙은 모든 클래스를 스프링 빈으로 등록한다 (싱글톤) 빈 기본 이름은 클래스명을 사용한다. 맨 앞글자만 소문자로 사용 스프링 빈의 이름을 직접 지정하고 싶으면 @Component(“…”) 와 같이 직접 지정
@Autoriwred 의존관계 자동 주입 스프링 컨테이너가 자동으로 해당 스프링 빈을 찾아서 주입 기본 조회 전략은 타입이 같은 빈을 찾아서 주입
지정하지 않으면 @ComponentScan이 붙은 설정 정보 클래스의 패키지가 시작 위치가 된다
권장 방법 : 패키지 위치를 지정하지 않고, 설정 정보 클래스의 위치를 프로젝트 최상단에 둔다
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
애노테이션은 상속 관계가 없다. 자바 언어에서 지원하는 기능이 아니라 스프링이 지원하는 기능 useDefaultFilters 기본이 켜져있는데 끄면 기본 스캔 대상들이 제외
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
annotation class MyExcludeComponent ()
@Configuration
@ComponentScan(includeFilters = [ComponentScan.Filter(classes = arrayOf(MyIncludeComponent::class))])
@ComponentScan(excludeFilters = [ComponentScan.Filter(type = FilterType.ANNOTATION, classes = arrayOf(MyExcludeComponent::class))])
class ComponentFilterAppConfig {
companion object {
}
}
스프링 부트는 컴포넌트 스캔을 기본으로 제공하는데 개인적으로 옵션을 변경하면서 사용하기 보다는 스프링의 기본 설정에 최대한 맞추어 사용하는 것을 권장하고 선호하는 편이다
컴포넌트 스캔에서 같은 빈 이름을 등록하면?
현실은 개발자 의도적으로 설정해서 이런 결과가 만들어지기보다는 여러 설정이 꼬여서 이런 결과가 만들어지는 경우가 대부분이다 정말 잡기 어려운 버그가 만들어진다. 항상 잡기 어려운 버그는 애매한 버그다
스프링에서는 자동빈과 수동빈 충돌시 스프링 부트 에러 발생
The bean 'memoryMemberRepository', defined in class path resource [core/studyspring/AutoAppConfig.class], could not be registered. A bean with that name has already been defined in file [/Users/user/study/study-spring/build/classes/kotlin/main/core/studyspring/member/MemoryMemberRepository.class] and overriding is disabled.
생성자에 @Autowired keyword 를 붙여주고 private final MemberRepository memberRepository; 생성자에는 값을 다 채워넣어야한다
생성자가 딱 1개만 있으면 @Autowired 를 생략해도 자동 주입이 된다 생성자 주입은 bean을 등록하고 의존관계 주입도 함께 일어난다
@Autowired
fun init(memberRepository: MemberRepository, discountPolicy: DiscountPolicy) {
this.memberRepository = memberRepository
this.discountPolicy = discountPolicy
}
의존관계 자동 주입은 스프링 컨테이너가 관리하는 스프링 빈이어야 동작한다. 스프링 빈이 아닌 클래스에서 @Autowired 코드를 적용해도 아무 기능도 동작하지 않는다
주입할 스프링 빈이 없어도 동작해야할 때가 있다 @Autowired만 사용하면 required 옵션의 기본값이 true 로 되어 있어서 자동 주입 대상이 없으면 오류가 발생
class AutowiredTest {
@Test
fun autowiredOption() {
val ac = AnnotationConfigApplicationContext(TestBean::class.java)
}
class TestBean {
companion object {
@Autowired(required = false) // 의존 관계가 없으면 메서드 자체가 호출되지 않는다
// @Autowired // member가 spring bean이 아니기 때문에 찾을 수 없다
fun setNoBean1(noBean1: Member) {
println("noBean1 = $noBean1")
}
@Autowired
fun setNoBean2(@Nullable noBean1: Member) {
println("noBean2 = $noBean1")
}
@Autowired
fun setNoBean3(noBean1: Optional<Member>) {
println("noBean3 = $noBean1")
}
}
}
}
@Nullable, Optional은 스프링 전반에 걸쳐서 지원된다. 생성자 자동 주입에서 특정 필드에만 사용해도 된다
과거에는 수정자 주입과 필드 주입을 많이 사용했지만, 최근에는 스프링을 포함한 DI 프레임워크 대부분이 생성자 주입을 권장한다
불변 대부분의 의존관계 주입은 한번 일어나면 애플리케이션 종료시점까지 의존관계를 변경할 일이 없다. 대부분의 의존관계는 애플리케이션 종료 전까지 변하면 안된다 (불변) 수정자 주입을 사용하면 setXxx 메서드를 public 으로 열어두어야한다 누군가 실수로 변경할 수도 있고, 변경하면 안되는 메서드를 열어두는 것은 좋은 설계 방법이 아니다 생성자 주입은 객체를 생성할 때 딱 1번만 호출되므로 이후에 호출되는 일이 없다. 따라서 불변하게 설계할 수 있다
누락 프레임워크 없이 순수한 자바코드를 단위테스트 하는 경우에 다음과 같이 수정자 의존관계 사용시 누락할 수 있음
val orderService = OrderServiceImpl()
// 의존관계 주입 : NPE 발생 의존관계가 눈에 보이지 않는다
생성자 주입 방식을 선택하는 이유는 프레임워크에 의존하지 않고, 순수한 자바 언어의 특징을 잘 살리는 방법 기본으로 생성자 주입을 사용하고, 필수 값이 아닌 경우에는 수정자 주입 방식을 옵션으로 부여하면 된다. 생성자 주입과 수정자 주입을 동시에 사용할 수 있다 항상 생성자 주입을 선택해라! 그리고 가끔 옵션이 필요하면 수정자 주입을 선택해라. 필드 주입은 사용하지 않는게 좋다
대부분이 불변이고 생성자에 final 키워드를 사용하게 된다 생성자와 주입받는 값을 코드로 만들어야한다 필드 주입처럼 좀 편리하게 사용하는 방법은 없을까?
@RequiredArgsConstructor 생성자와 final 을 사용
롬복 라이브러리가 제공하는 @RequiredArgsConstructor 기능을 사용하면 final이 붙은 필드들을 모아서 생성자를 자동으로 만들어준다 롬복이 자바의 애노테이션 프로세서라는 기능을 이용해서 컴파일 시점에 생성자 코드를 자동으로 생성해준다
생성자를 딱 1개 두고 @Autowired를 생략하는 방법을 주로 사용한다
타입으로 조회하기때문에 ac.getBean(DiscountPolicy::class) fixDiscountPolicy, RateDiscountPolicy 둘다 DiscountPolicy 타입이기 때문에 다음과 같은 에러가 발생한다
No qualifying bean of type 'core.studyspring.discount.DiscountPolicy' available: expected single matching bean but found 2: fixDiscountPolicy,rateDiscountPolicy
빈이 2개가 발견됨. 하위 타입으로 지정할 수 있으나 하위타입으로 지정하는 것은 DIP 위배. 유연성이 떨어짐.
조회 대상 빈이 2개 이상일 때 해결 방법
추가 구문자로 붙여주는 방법. 주입시 추가적인 방법을 제공하는 것.
@Qualifier(“mainDiscountPolicy”) 를 못찾으면 어떻게 될까?
mainDiscountPolicy 라는 이름의 스프링 빈을 추가로 찾는다.
@Primary 사용 (가장 권장하는 방법) 우선순위를 정하는 방법. @Autowired 시 여러 빈 매칭되면 @Primary가 우선권을 가진다
@Qualifier의 단점 : 코드마다 @Qualifier를 붙여줘야한다.
코드에서 자주 사용하는 메인 데이터베이스의 커넥션을 획득하는 스프링 빈이 있고, 코드에서 특별한 기능으로 가끔 사용하는 서브 데이터베이스의 커넥션을 획득하는 스프링 빈이 있다.
메인 데이터베이스의 커넥션을 획득하는 스프링 빈은 @Primary 를 적용해서 조회하는 곳에서 @Qualifier 지정 없이 편리하게 조회하고, 서브 데이터베이스 커넥션 빈을 획득할 때는 @Qualifier 를 지정해서 명시적으로 획득 하는 방식으로 사용하면 코드를 깔끔하게 유지할 수 있다. 물론 이때 메인 데이터베이스의 스프링 빈을 등록할 때 @Qualifier 를 지정해주는 것은 상관없다.
@Qualifier(“mainDiscountPolicy”) 컴파일시 타입 체크가 안된다 직접 애노테이션으로 컴파일시 확인가능
@Target(ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Qualifier("mainDiscountPolicy")
annotation class MainDiscountPolicy(
애노테이션에는 상속이라는 개념이 없다. 이렇게 여러 애노테이션을 모아서 사용하는 기능은 스프링이 지원해주는 기능이다. @Qulifier 뿐만 아니라 다른 애노테이션들도 함께 조합해서 사용할 수 있다. 단적으로 @Autowired도 재정의 할 수 있다. 물론 스프링이 제공하는 기능을 뚜렷한 목적 없이 무분별하게 재정의 하는 것은 유지보수에 더 혼란만 가중할 수 있다.
의도적으로 정말 해당 타입의 스프링 빈이 다 필요한 경우도 있음 ex. 할인 서비스를 제공하는데, 클라이언트가 할인의 종류(rate, fix)를 선택할 수 있다고 가정해보자. 스프링을 사용하면 소위 말하는 전략 패턴(Strategy Pattern)을 매우 간단하게 구현할 수 있다.
val ac = AnnotationConfigApplicationContext(AutoAppConfig::class.java, DiscountService::class.java)
val discountService = ac.getBean(DiscountService::class.java)
class DiscountService {
fun discount(member: Member, price: Int, discountCode: String): Int {
val discountPolicy = policyMap.get(discountCode)
return discountPolicy?.discount(member, price)!!
}
var policyMap: Map<String, DiscountPolicy>
var policies : List<DiscountPolicy>
@Autowired
constructor(policyMap: Map<String, DiscountPolicy>, policies : List<DiscountPolicy>) {
this.policies = policies
this.policyMap = policyMap
println("policies $policies")
println("policyMap $policyMap")
}
}
결론부터 이야기하면, 스프링이 나오고 시간이 갈 수록 점점 자동을 선호하는 추세다.
스프링은 @Component 뿐만 아니라 @Controller , @Service , @Repository 처럼 계층에 맞추어 일반적인 애플리케이션 로직을 자동으로 스캔할 수 있도록 지원
스프링 부트는 컴포넌트 스캔을 기본으로 사용하고, 스프링 부트의 다양한 스프링 빈들도 조건이 맞으면 자동으로 등록
설정 정보를 기반으로 애플리케이션을 구성하는 부분과 실제 동작하는 부분을 명확하게 나누는 것이 이상적이지만, 개발자 입장에서 스프링 빈을 하나 등록할 때 @Component 만 넣어주면 끝나는 일을 @Configuration 설정 정보에 가서 @Bean 을 적고, 객체를 생성하고, 주입할 대상을 일일이 적어주는 과정은 상당히 번거로움
관리할 빈이 많아서 설정 정보가 커지면 설정 정보를 관리하는 것 자체가 부담. 그리고 결정적으로 자동 빈 등록을 사용해도 OCP, DIP를 지킬 수 있음
업무 로직 :
기술 지원 빈: 기술적인 문제나 공통 관심사(AOP)를 처리할 때 주로 사용. 데이터베이스 연결이나, 공통 로그 처리 처럼 업무 로직을 지원하기 위한 하부 기술이나 공통 기술들
애플리케이션에 광범위하게 영향을 미치는 기술 지원 객체는 수동 빈으로 등록해서 딱! 설정 정보에 바로 나타나게 하는 것이 유지보수 하기 좋다.
편리한 자동 기능을 기본으로 사용하자 직접 등록하는 기술 지원 객체는 수동 등록 다형성을 적극 활용하는 비즈니스 로직은 수동 등록을 고민해보자
배경 데이터베이스 커넥션 풀이나, 네트워크 소켓처럼 애플리케이션 시작 시점에 필요한 연결을 미리 해두고, 애플리케이션 종료 시점에 연결을 모두 종료하는 작업을 진행하려면, 객체의 초기화와 종료 작업이 필요
스프링 빈의 이벤트 라이프 사이클 스프링컨테이너생성 -> 스프링빈생성 -> 의존관계주입(setter) -> 초기화콜백 사용 -> 소멸전콜백 -> 스프링 종료
객체의 생성과 초기화를 분리하자 (단일 책임 원칙)
스프링의 빈 생명주기 콜백 방법
(1) 인터페이스 설정 방법 - 단점
(2) 빈 등록 초기화, 소멸 메서드 지정
(3) @PostConstruct @PreDestroy 애노테이션 특징
javax.annotation.PostConstruct
)싱글톤: 기본 스코프, 스프링 컨테이너의 시작과 종료까지 유지되는 가장 넓은 범위의 스코프
프로토타입: 스프링 컨테이너는 프로토타입 빈의 생성과 의존관계 주입까지만 관여하고 더는 관리하지 않는 매우 짧은 범위의 스코프
웹 관련 스코프
프로토타입 빈 특징
프로토타입 스코프 - 싱글톤 빈과 함께 사용시 문제점
프로토타입프로바이더를 사용한 해결
@Scope("singleton")
class ClientBean {
@Autowired
lateinit var prototypeBeanProvider: ObjectProvider<PrototypeBean>
// spring container 가 Prototype을 생성시점에 주입한다
fun logic(): Int {
// 찾아주는 기능 제공
val prototypeBean = prototypeBeanProvider.getObject()
prototypeBean.addCount()
return prototypeBean.getCount()
}
}
ObjectProvider 의 getObject 호출시 내부에서 스프링 컨테이너를 통해 빈을 찾아서 반환
특징
스프링이 아닌 자바 표준 사용
@Scope("singleton")
class ClientBean {
@Autowired
lateinit var prototypeBeanProvider: Provider<PrototypeBean>
// spring container 가 Prototype을 생성시점에 주입한다
fun logic(): Int {
// 찾아주는 기능 제공
val prototypeBean = prototypeBeanProvider.get()
prototypeBean.addCount()
return prototypeBean.getCount()
}
}
특징
javax.inspect
)정리
스프링 고유 기술 vs 자바 표준 기술
웹스코프 특징
웹 스코프 종류
spring-boot-starter-web 라이브러리를 추가하면 스프링 부트는 내장 톰켓 서버를 활용해서 웹 서버와 스프링을 함께 실행시킨다.
스프링 부트는 웹 라이브러리가 없으면 AnnotationConfigApplicationContext 을 기반으로 애플리케이션을 구동
웹 라이브러리가 추가되면 웹과 관련된 추가 설정과 환경들이 필요하므로 AnnotationConfigServletWebServerApplicationContext 를 기반으로 애플리케이션을 구동
@Controller
class LogDemoController(
val logDemoService: LogDemoService,
val myLogger: MyLogger
) {
@RequestMapping("log-demo")
@ResponseBody // responseBody면 문자열 그대로 내보낼 수 있다
fun logDemo(request: HttpServletRequest): String {
val requestUrl = request.requestURL.toString()
myLogger.setRequestUrl(requestUrl)
myLogger.log("controller test")
logDemoService.logic("testId")
return "OK"
}
}
@Component
@Scope(value = "request")
class MyLogger {
private lateinit var uuid: String
private lateinit var requestUrl: String
fun setRequestUrl(requestUrl: String) {
this.requestUrl = requestUrl
}
fun log(message: String) {
println("[$uuid][$requestUrl] $message")
}
@PostConstruct
fun init() {
// 빈이 생성되는 시점에 uuid 생성하고 저장
this.uuid = UUID.randomUUID().toString()
println("[$uuid] request scope bean create: $this")
}
@PreDestroy
fun close() {
println("[$uuid] request scope bean close: $this")
}
}
@Controller
class LogDemoController(
val logDemoService: LogDemoService,
val myLoggerProvider: ObjectProvider<MyLogger>
) {
@RequestMapping("log-demo")
@ResponseBody // responseBody면 문자열 그대로 내보낼 수 있다
fun logDemo(request: HttpServletRequest): String {
val myLogger = myLoggerProvider.getObject()
val requestUrl = request.requestURL.toString()
println("myLogger = ${myLogger.javaClass}")
myLogger.setRequestUrl(requestUrl)
myLogger.log("controller test")
logDemoService.logic("testId")
return "OK"
}
}
@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
// HTTP 요청당 하나씩 생성되고 HTTP 요청이 끝나는 시점에 소멸됨
// Proxy 사용시 이렇게 하면 MyLogger의 가짜 프록시 클래스를 만들어두고 HTTP request와 상관 없이 가짜 프록시 클래스를 다른 빈에 미리 주입해 둘 수 있다.
class MyLogger {
private lateinit var uuid: String
private lateinit var requestUrl: String
fun setRequestUrl(requestUrl: String) {
this.requestUrl = requestUrl
}
fun log(message: String) {
println("[$uuid][$requestUrl] $message")
}
@PostConstruct
fun init() {
// 빈이 생성되는 시점에 uuid 생성하고 저장
this.uuid = UUID.randomUUID().toString()
println("[$uuid] request scope bean create: $this")
}
@PreDestroy
fun close() {
println("[$uuid] request scope bean close: $this")
}
}
myLogger = class hello.core.common.MyLogger$$EnhancerBySpringCGLIB$$b68b726d
스프링의 역사
스프링 이전은 EJB(Enterprise Java Beans) 사용. Spring+JPA+ORM 종합 선물 세트 but 복잡하고 어렵다
EJB 컨테이너 -> 스프링 EJB Entity 빈 기술 -> Hibernate. JPA(자바 표준 정의)
로드 존슨 책 출간 (
J2EE Development Without EJB
)스프링은 전통적인 J2EE(EJB)라는 겨울을 넘어 새로운 시작을 뜻.. 😰
스프링 프레임워크 역사
2003 Spring Framework 1.0 - XML 기반 설정 2006 Spring Framework 2.0 - XML 편의 기능 지원 2009 Spring Framework 3.0 - 자바 코드 지원 2013 Spring Framework 4.0 - 자바 8 지원 2014 Spring Boot 1.0 (스프링 설정을 편리하게 해주는) 2017 Spring Framework 5.0, Spring Boot 2.0 - reactive 프로그래밍 지원 (ex. non-blocking) 2020 Spring Fraemwork 5.2, Spring Boot 2.3
스프링 종류
https://spring.io/projects
스프링 프레임워크
스프링 부트
스프링 데이터 : 데이터 CUD 기능 제공 (ex. spring JPA) 스프링 세션 : 세션 기능 편리하게 제공 스프링 시큐리티 : 보안 스프링 Rest Docs : API 문서화 스프링 배치 : batch 처리 특화 스프링 클라우드 : 클라우드 기술 특화
스프링을 만든 이유?