일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
- csv파일
- server.xml
- csv
- @ResponseBody
- map리턴
- map return
- Ajax
- jQeury
- json 접근
- json
- 제이쿼리
- 스케줄 2번실행
- centos
- 원하는 Mysql 버전 설치
- mysql8버전 설치 #mysql8 tar설치
- vo리턴
- 카프카 #Kafka
- spring
- 스케줄 중복실행
- 가상머신
- ElasticSearch
- 엘라스틱서치
- ubunt mysql8
- JSONView
- Simple Json
- context-quartz.xml
- kibana
- 파일 쓰기
- vm
- 파일 읽기
- Today
- Total
streetprogrammer
스프링 핵심 원리 (기본편) 본문
[좋은 객체 지향 설계의 5가지 원칙 (SOLID)]
- SRP : 단일 책임 원칙 (한 클래스는 하나의 책임만 가져야 한다.)
- OCP : 개방-폐쇄 원칙 (확장에는 열려 있으나 변경에는 닫혀 있어야 한다.)
- LSP : 리스코프 치환 원칙 (부모타입에서 자식타입으로 변경해도 실행 문제가 없어야 한다.)
- ISP : 인터페이스 분리 원칙(인터페이스는 기능에 따라 분리 되어야 한다)
- DIP : 의존관계 역전 원칙 (구현 클래스에 의존하지 말고, 인터페이스에 의존해야 한다.)
기존 설계는 service에서 discountPolicy를 받을떄 new FixDiscountPolicy를 활용하기 때문에
추상화가아닌 추체클래스도 의존을 한다 => “DIP 위반”
또한,
service에서 기존 FixDisCountPolicy가 아닌 RateDisCountPolicy로 변경할 경우 service내부의 코드를 수정해야 한다. => “OCP 위반”
이를 해결하기 위해 AppConfig클래스 활용
[AppConfig.class]
AppConfig에서는 구성 정보의 역할과 구현을 명확하게 분리하고 연결함
이렇듯 프로그램에 대한 제어흐름에 대한 모든 권한을 가지고 관리하는것을 제어의 역전 (IoC)라고 한다.
이를 IoC컨테이너 혹은 DI컨테이너 라고 한다.
이러한 AppConfig를 더 효율적으로 관리 해주기 위해 스프링을 사용한다.
@Configuration를 AppConfig상위에 해당 어노테이션을 추가하고 매서드 상단에 @Bean을 추가한다.
[스프링 생성 및 사용]
1.처음 스프링이 시작되면 @Configuration 어노테이션을 확인 하고 해당 클래스 안에
@Beand어노테이션들은 스프링 빈 저장소에 추가한다.
2.이떄 해당 메서드이름이 빈이름으로 자동 등록된다. (beanName은 수정가능 근데 하지마)
3.이후 설정 정보를 참고해서 싱글톤 패턴으로 의존관계가 주입(DI)된다.
// 스프링컨테이너 생성
ApplicationContext ac = new AnnotationcConfigApplicationContext(AppConfig.class);
- ApplicationContext : 스프링컨테이너를 의미 (인터페이스)
- AnnotationcConfigApplicationContext : 스프링 구현 클래스
- 모든 빈 출력하기
@Test
@DisplayName("모든 빈 출력")
void findAllBean(){
String[] beanDefinitionNames = ac.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames){
Object bean = ac.getBean(beanDefinitionName);
System.out.println("name = " + beanDefinitionName + " object = " + bean);
}
}
- 빈 유형에 따라 출력하기
if(beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION){
Object bean = ac.getBean(beanDefinitionName);
System.out.println("name = " + beanDefinitionName + " object = " + bean);
}
- 빈 타입으로 조회
@Test
@DisplayName("빈 없이 타입으로만 조회")
void findBeanByType() {
MemberService memberService = ac.getBean(MemberService.class);
assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}
- 이때 해당 타입이 여러개일경우 NoUniqueBeanDefinitionException
- 빈 이름으로 조회
@Test
@DisplayName("빈 이름으로 조회")
void findBeanByName() {
MemberService memberService = ac.getBean("memberService", MemberService.class);
assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}
- 이때 해당 이름 이 없을 경우 NoSuchBeanDefinitionException
- 모든 특정 타입 빈 조회
@Test
@DisplayName("특정 타입을 모두 조회하기")
void findAllBeanByType(){
Map<String, MemberRepository> beansOfType = ac.getBeansOfType(MemberRepository.class);
for (String key : beansOfType.keySet()){
System.out.println("key = " + key + " value = " + beansOfType.get(key));
}
System.out.println("beansOfType = " + beansOfType);
assertThat(beansOfType.size()).isEqualTo(2);
}
- 부모 빈 타입 조회시 자식 빈들까지 다 나옴 (object를 최고 부모)
[@Congiuration]
목표 : @Congiuration의 역할 알아보기
싱글톤을 유지시키기위해 사용한다.
해당 어노테이션을 사용하면 클래스가 복사되어 가짜 클래스를 사용하게됨
[컴포넌트 스캔과 의존관계 자동 주입 시작하기]
목표 : 수동으로 빈등록하지말고 편리한 자동주입을 사용해보자
1. @ComponentScan 등록 ( 스프링부트에서는 되어있음)
@Configuration
@ComponentScan(
excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = Configuration.class)
)
public class AutoAppConfig {
}
* Confiuration은 제외하기 위해 excludeFilters를 추가함 why @Confiuration때문에 수동 빈들도 등록되기 떄문에 컴포넌트 스캔이 제대로 작동됐는지 확인이 안됨
2. 모든 클래스에 @Component 등록
@Component
public class MemberServiceImpl implements MemberService{
[생성자 주입을 해라!]
목표 : DI할떄 가장 좋은 방법 알아보기
장점
1. 불변 : 객체를 생성할때 1번만 호출되므로 불변하게 설계할 수 있다.
2. 누락 : 프레임워크 없이 순수한 자바 코드를 단위 테스할때 누락되지 않는다.
3. final : final 키워드 사용가능 값이 설정되지 않는 오류를 컴파일러 시점에서 막아준다.
[롬복 라이브러리 사용]
목표 : 더 편리하게 DI와 Getter/Setter , toString 하기
1. build.gradle에서 추가
//lombok 라이브러리 추가 시작
compileOnly 'org.projectlombok:lombok'
2. 플러그인 설치 여부 확인
file > settings > Plugins 에서 lombok 검색 후 설치
3. file > settings > Compiler > Annotation Processors 에서 Enable annotation processing 체크
사용방법
- 상단에 어노테이션 등록하면 사용할 수 있다.
@Getter
@Setter
@ToString
public class HelloLombok {
private String name;
private int age;
lombok으로 생성자 만드는방법
-현재 클래스에서 final로 만들어진 변수의 생성자를 자동으로 만들어준다.
@Component
@RequiredArgsConstructor
public class OrderServiceImpl implements OrderService{
/**
* lombok 사용하기 : @RequiredArgsConstructor통해서 생성자를 생략해도 자동으로 만들어짐
* 단, final 필수
*/
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
[조회 빈이 2개 이상 -문제]
목표 : 조회된 빈이 두개 이상일떄 발생하는 오류 해결하기
1. @Autowired 필드명
- 생성자 주입시 필드 명을 통해서 원하는 구현체의 빈을 찾아올수 있다.
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
public OrderServiceImpl(MemberRepository memberRepository,
DiscountPolicy rateDiscountPolicy) { // discountPolicy -> rateDiscountPolicy
this.memberRepository = memberRepository;
this.discountPolicy = rateDiscountPolicy;
}
2. @Qualifire ( 추가 구문자 )
@Component
@Qualifier("mainDiscountPolicy")
public class RateDiscountPolicy implements DiscountPolicy{
- @Quilifier 등록 후 사용
public OrderServiceImpl(MemberRepository memberRepository,
@Qualifier("mainDiscountPolicy") DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
* 만약 못찾으면 해당 이름의 스프링 빈을 찾는다 그래도 없으면 예외
3. @Primay
@Component
@Primary
public class RateDiscountPolicy implements DiscountPolicy{
* @Primay로 등록하면 해당 클래스가 우선권을 가지게 된다. 주로 많이 사용됨
[조회한 빈이 모두 필요할 떄, List, Map]
목표 : 클라이언트가원하는 구현객체 이용하기 (동적으로 빈 선택하기)
1. DiscountPolicy 인터페이스를 Map으로 받기 (정액제 , 할인제)
@Test
void findAllBean(){
ApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class, DiscountService.class);
DiscountService discountService = ac.getBean(DiscountService.class);
Member member = new Member(1L,"userA", Grade.VIP);
int discountPrice = discountService.discount(member,10000, "fixDiscountPolicy");
2. 클라이언트가 원하는 discount 구현체를 선택 후 discount 결과값 넘겨주면 Map에서 해당데이터 리턴
static class DiscountService {
private final Map<String, DiscountPolicy> policyMap;
private final List<DiscountPolicy> policies;
public DiscountService(Map<String, DiscountPolicy> policyMap, List<DiscountPolicy> policies) {
this.policyMap = policyMap;
this.policies = policies;
}
public int discount(Member member, int price, String discountCode) {
DiscountPolicy discountPolicy = policyMap.get(discountCode);
return discountPolicy.discount(member, price);
}
}
*클래스를 바로 new AnnotationConfigApplicationContext에 등록하면 빈등록이 됨
[자동, 수동의 올바른 실무 운영 기준]
목표 : 자동을 쓰다가 언제 수동 빈 등록을 사용하면 좋을지 알아보기
결론 : 자동을 주로 사용하자 why 자동으로도 OCP, DIP를 지킬수있다.
*명확히 나타내려면 수동을 써야함.
[빈 생명주기 콜백]
목표 : 원하는 어플리케이션이 종료되면 빈도 사용종료
@Test
public void lifeCycleTest(){
ConfigurableApplicationContext ac = new AnnotationConfigApplicationContext(LifeCycleConfig.class);
NetworkClient client = ac.getBean(NetworkClient.class);
ac.close();
}
*스프링을 종료시키려면 기존 ApplicationContext 말고 ConfigurableApplicationContext가 필요
*스프링 컨테이너 생성 -> 스프링 빈 생성 -> 의존관계 주입-> 초기화 콜백 -> 사용 -> 소멸전 콜백 -> 스프링 종료
* 객체를 생성하는 부분과 초기화는 분리해야됨 초기화의 경우 외부 커넥션을 연결하는 등 무거운 동작을 수행하기때문
[인터페이스로 초기화 소멸전 콜백]
public class NetworkClient implements InitializingBean, DisposableBean {
InittializingBean : 의존관계 주입이 끝난 후 실행하는 메서드를 가지고 있음DisposableBean : 종료전 콜백 메서드를 가지고 있음
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("NetworkClient.afterPropertisSet");
connect();
call("초기화 연결 메세지");
}
@Override
public void destroy() throws Exception {
disconnect();
}
단점 (사용안함)- 스프링 전용 인터페이스로 스프링전용 인터페이스에 의존한다.- 초기화 소멸 메서드의 이름을 변경할 수 없다.- 외부 라이브러리에 적용할 수 없다.
[빈 등록 초기화,소멸 메서드]
@Configuration
static class LifeCycleConfig {
@Bean(initMethod = "init", destroyMethod = "close")
public NetworkClient networkClient(){
NetworkClient networkClient = new NetworkClient();
networkClient.setUrl("http://hello-core.dev");
return networkClient;
}
}
* @Bean에다가 초기화메서드 이름과 종료메서드 이름을 등록해 사용한다.
public void init(){
System.out.println("NetworkClient.afterPropertisSet");
connect();
call("초기화 연결 메세지");
}
public void close(){
disconnect();
}
장점
- 메서드 이름을 자유롭게 지정
- 스프링 빈이 스프링 코드에 의존하지 않는다
- 외부라이브러리에도 초기화, 종료 메서드를 적용할 수 있다.
추가로 @Bean의 destoryMethod는 디폴트로 종료메서드를 찾아서 동작시킴 (shutdow, close 등 )
만약 해당 기능을 false로 놓고 싶으면 destoryMethod = ""로 등록하세요.
[어노테이션을 이용한 방법]
@PostConstruct/@PreDestroy 어노테이션을 사용하면됨
@PostConstruct
public void init(){
System.out.println("NetworkClient.afterPropertisSet");
connect();
call("초기화 연결 메세지");
}
@PreDestroy
public void close(){
disconnect();
}
단점- 외부라이브러리에서 적용 불가능 잠정- 가장 많이 사용하고있음 그냥 이거쓰세요.
[빈스코프] (빈 생명주기)
목표 : 스코프에 대한 기초개념을 학습
싱글톤 스코프 : 컨테이너의 시작과 종료까지 유지되는 가장 넒은 범위의 스코프이다. 기본적으로 싱글톤스코프를 지원
프로토 타입 스코프 : 빈의 생성과 의존관계 주입 까지만 관여한다.
웹 관련 스코프
1. request : 웹 요청이 들어오고 나갈떄 까지 유지되는 스코프
2. session : 웹세션이 생성되고 종료될 떄 까지 유지되는 스코프
3. application : 웹의 서블릿 컨텍스와 같은 범위로 유지되느 스코프
[프로토타입 스코프]
목표 : 프로토타입 스코프의 심화개념 학습
@Scope("prototype")
static class PrototypeBean {
@PostConstruct
public void init(){
System.out.println("PrototypeBean.init");
}
@PreDestroy
public void destory(){
System.out.println("PrototypeBean.destory");
}
}
프로토타입의 경우 요청할떄마다 새로운 인스턴스를 생성해서 의존관계 부여 후 초기화 처리한뒤 반환한다. 이후로 관여안함 즉 종료는 클라이언트가 마무리 지어야함
[프로토타입 스코프와 싱글톤 빈이 함꼐 사용시 문제점]
- 싱글톤안에 프로토타입이 요청될 경우 단 한번의 요청 후 변경되지 않아 사용자가 원하는 프로토타입의 효과를 볼수가없다. 이를 해결하려면 해당 매서드에서 applicationConext~~를 불러야함 이건 너무 무식
[프로토타입 빈 - 싱글톤 빈과 사용시 Provider로 문제해결]
목표 : 원하는 빈만 찾기(DL : dependency lookup) 위해서 Provider을 이용해 프로토타입 빈을 찾아 이용한다.
1. ObjectProvider 방법
@Scope("singleton")
static class ClientBean{
@Autowired
private ObjectProvider<PrototypeBean> prototypeBeanObjectProvider;
public int logic(){
PrototypeBean prototypeBean = prototypeBeanObjectProvider.getObject();
prototypeBean.addCount();
int count = prototypeBean.getCount();
return count;
}
}
하지만 스프링에서 만들어주기떄문에 스프링에 의존적이다.
2. JSR303 Provider 방법
* build.gradle에 추가한다.
implementation 'javax.inject:javax.inject:1'
@Scope("singleton")
static class ClientBean{
@Autowired
// private ObjectProvider<PrototypeBean> prototypeBeanObjectProvider;
private Provider<PrototypeBean> prototypeBeanObjectProvider;
public int logic(){
// PrototypeBean prototypeBean = prototypeBeanObjectProvider.getObject();
PrototypeBean prototypeBean = prototypeBeanObjectProvider.get();
prototypeBean.addCount();
int count = prototypeBean.getCount();
return count;
}
}
* 프로토 타입빈은 매번 사용할 떄 마다 의존관계 주입이 완료된 새로운 객체가 필요하면 사용한다. 하지만 드믈다.
[웹스코프] - 종료메서드까지 호출해줌
1. request : http 요청마다 들어오고 나갈때까지 유지되는 스코프
- 동시에 2명이 요청하면 각각 다른 스프링빈이 생성되서 사용할 수 있음
- 요청이 같은경우 같은 스프링빈을 사용한다.
2. session : http Session과 동일한 생명주기를 가지는 스코프
3. application 서블릿 컨텍스트와 동일한 생명주기
4. websocket : 웹소켓과 동일한 생명주기
[request 스코프 예제 만들기]
목표 : 예제를 통해 request 스코프 이해하기
상황 : 여러 요청을 로그로 남길때 request 스코프와 UUID를 활용해 개발해보자
문제 : request 스코프 자체는 사용자가 요청시 만들어지는데 스프링이 작동될떄는 존재하지않음 => 에러 발생
해결 : Provider를 활용해 해결해보자
@Controller
@RequiredArgsConstructor
public class LogDemoController {
private final LogDemoService logDemoService;
// private final MyLogger myLogger;
private final ObjectProvider<MyLogger> myLoggerObjectProvider;
@RequestMapping("log-demo")
@ResponseBody
public String logDemo(HttpServletRequest request) {
MyLogger myLogger = myLoggerObjectProvider.getObject();
String requestURL = request.getRequestURL().toString();
myLogger.setReqeustURL(requestURL);
[스코프와 프록시]
목표 : proxyMode를 추가하면 기본의 Provider를 사용안해도된다.
프록시는 가짜를 만들어줌 이떄 TARGET_은 대상이 class일경우 CLASS interface일 경우 INTERFACE로 지정
@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyLogger {
@Service
@RequiredArgsConstructor
public class LogDemoService {
// private final ObjectProvider<MyLogger> myLoggerObjectProvider;
private final MyLogger myLogger;
public void logic(String id) {
// MyLogger myLogger = myLoggerObjectProvider.getObject();
myLogger.log("service id = " + id);
}
}
포트변경properties에서
server.port= 9090