Open otakijae opened 1 year ago
Java 11 설치 / spring boot 2.7.3 ==> spring framework core 5
Spring Web, Thymeleaf
컨트롤러에서 리턴 값으로 문자를 반환하면 뷰 리졸버(viewResolver)가 화면을 찾아서 처리한다.
build/run
./gradlew build
cd build/libs
java -jar hello-spring-0.0.1-SNAPSHOT.jar
Controller / Service / Repository 의존관계 구조 with MVC architecture
Spring Framework로 웹 개발을 할 땐 기본적으로 MVC 패턴을 따른다. MVC 패턴은 Model, View, Controller 이 3가지로 나뉘어 역할을 분할하여 처리한다. 역할을 나누어 처리하기 때문에 서로의 결합도가 낮아져서 좋은 코드가 되며 유지보수도 하기 편해진다.
Controller
사용자가 접근 한 URL에 따라 요청을 파악한다. URL에 맞는 Method를 호출하여 Service와 함께 Business Logic을 처리한다. 최종적으로 나온 결과는 Model에 저장을 하고, View에 던져준다.
Model은 Controller에서 받은 데이터를 저장하는 역할을 한다.
Controller로 부터 받은 Model 데이터를 바탕으로 사용자에게 표현해준다. 일반적으로 HTML, JSP... 에 해당한다.
스프링 처음 시작하면, 스프링 부트 내부 컨테이너라는 전체 통이 생기는데, 거기에 @Controller 가 있으면, ~~Controller를 객체를 생성해서 컨테이너에 넣어둠. 그리고 스프링이 관리함
그 다음에 MemberService를 가져다가 사용해야하기 때문에, 사용하게 되는 Controller에 등록해서 사용함. 다른 아무데서나 해당 MemberService를 사용하지 못하도록 하는 것 같음. dependency를 일부러 주는 것 같음
근데 이 dependency는 Interface에 의존성을 가지게 구현함. 특정 Interface 구현체 인스턴스? 클래스?를 명시해서 그 객체에 의존성을 가지게 하지 않음
컴포넌트 스캔과 자동 의존관계 설정
실무고민
스프링은 크게 스프링 프레임워크/스프링 부트 + JPA 라고 볼 수 있음.
스프링 부트
스프링을 편리하게 사용할 수 있도록 지원. Tomcat같은 웹 서버를 내장했기 때문에 별도의 웹 서버 설치 X. 스프링 부트는 스프링 프레임워크와 별도로 사용할 수 없음
스프링 프레임워크
스프링 DI 컨테이너, AOP, 이벤트, 기타 등 핵심 기술들을 말함.
스프링 MVC, 트랜잭션, JDBC, ORM/XML 지원 등
JPA
'Hibernate' 를 가지고 자바 표준인 JPA 를 만듦. 실무에서의 편리함과 표준에서 오는 안정감에 의해 많이 사용되고 있음
- ORM - 객체와 관계형 데이터베이스의 데이터를 자동으로 매핑(연결)해주는 것
EJB의 지옥에 공감하게 됨
객체지향의 단점에 대해 한번더 생각해봄
변경 가능성이 없는 코드에 대해 굳이 추상화를 통해 설계를 하게되면, 추상화 클래스를 한번 더 열어봐야 하기 때문에 불필요한 시간 소비가 됨.
하지만 개발 단계에서 구체화에 대해 결정하는 시점을 미룰 수 있고 다형성을 유지할 수 있음.
이는 큰 장점이라고 생각함!
좋은 객체지향이란 무엇인지에 대해 더 깊은 공부를 해야한다고 느낌
SOLID 에 대해 한번 더 리뷰하고, 아직 명확하게 이해되지 않아서 다시 한번 볼 것.
스프링 DI나 IoC가 무엇일까?
스프링 DI 란?
Spring DI 컨테이너가 관리하는 객체를 빈 (Bean) 이라고 하고, 이 빈 (Bean)들을 관리한다는 의미로 컨테이너를 빈 팩토리 (BeanFactory) 라고 부른다.
이 빈 팩토리 (BeanFactory) 를 상속 받아 여러 가지 컨테이너 기능을 추가하여 'ApplicationContext' 라고 부르고 쓰임
Detail)
- BeanFactory
- Bean을 등록,생성,조회,반환 관리함.
- 보통은 BeanFactory를 바로 사용하지 않고, 이를 확장한 ApplicationContext를 사용함.
- getBean() 메서드가 정의되어 있음
- ApplicationContext
- Bean을 등록,생성,조회,반환 관리하는 기능은 BeanFactory와 같음
- Spring의 각종 부가 서비스를 추가로 제공함
- Spring이 제공하는 ApplicationContext 구현 클래스가 여러 가지 종류가 있음
Example) 출처 : https://www.nextree.co.kr/p11247/
IoC/DI 가 적용되지 않은 예시
package kr.co.nextree; public class Foo{ private Bar bar; public Foo() { bar = new SubBar(); } }
IoC/DI 가 적용된 예시
// 컨테이너 <beans> <bean id="bar" class="kr.co.nextree.SubBar"> <bean id="foo" class="kr.co.nextree.Foo"> <property name="bar" ref="bar" /> </bean> </beans>
// 애플리케이션 코드 package kr.co.nextree; public class Foo{ private Bar bar; public void setBar(Bar bar){ this.bar = bar; } }
Application 코드에서 setter 함수의 매개변수로 받아와서 실행 시 동적으로 의존관계를 설정.
스프링 DI 에 의존성을 주입하는 3가지 유형 1) 생성자를 이용한 의존성 삽입 2) Setter() 메소드를 이용한 의존성 삽입 3) 초기화 인터페이스를 사용한 의존성 삽입
단점뿐인 EJB의 시대가 있었기 때문에 Spring이 나오게 되었다. EJB는 복잡하고, 느리고, 객체 지향적이지도 않았다.
자바 언어 기반의 프레임워크로, 객체 지향 언어의 특징을 살려내는 프레임워크
클라이언트를 변경하지 않고, 서버의 구현 기능을 유연하게 변경할 수 있다.
원칙 | 설명 |
---|---|
SRP(Single Responsibility principle) | 한 클래스는 하나의 책임만 가져야 한다. |
OCP(Open/closed principle) | 확장에는 열려 있으나, 변경에는 닫혀 있어야 한다. |
LSP(Liskov substitution principle) | 하위 타입의 인스턴스로 바꿀 수 있어야 한다. |
ISP(Interface segregation principle) | 범용 인터페이스보다 여러 인터페이스가 낫다. |
DIP(Dependency inversion principle) | 추상화에 의존하자. 인터페이스에만 의존하라. |
https://www.quickprogrammingtips.com/spring-boot/history-of-spring-framework-and-spring-boot.html
많은 분들이 Section 1에 대한 내용을 준비할 것 같아서, Section 1을 들은 내용을 정리하기 보다는 궁금했던 부분들에 대해 확인해보며 다양한 방법의 장단점에 대하여 공유하고자 한다.
해당 챕터에서 스프링은 좋은 객체지향 프로그래밍을 위해 만들어진 프레임워크다라는 것을 알았고, SOLID에서 DI 없이 지킬 수 없었던 OCP와 DIP를 Spring에서 IoC와 DI를 통해 해결한다는 것을 이해할 수 있었다. 이를통하여 현재 개발자들이 Spring을 통하여 좋은 객체지향 프로그래밍을 할 수 있는 것이다.
실제 코드를 통해 알아 보았다. 아래와 같은 Car라는 Interface가 있다.
public interface Car {
int go();
}
해당 인터페이스를 구현하는 구현체 k5, k7이 아래와 같이 작성되있다고 가정해보자. 참고로 @Compenent의 경우 해당 클래스를 Spring Bean으로 등록한다는 것을 말한다.
@Component
public class k5 implements Car{
@Override
public int go() {
return 5;
}
}
@Component
public class k7 implements Car{
@Override
public int go() {
return 7;
}
}
그리고 테스트를 진행해 보았다.
@Autowired
private Car car;
참고로, @Autowired의 경우 Car Interface에 맞는 Bean으로 등록 된 객체를 가져와 Spring에서 자동으로 DI를 해달라고 명시해주는 어노테이션이다. 하지만, car 부분에 빨간줄이 생기며 제대로 DI가 안된다는 것을 알 수 있었다.
원인의 에러는 다음과 같았다.
Could not autowire. There is more than one bean of 'Car' type.
Beans: k5 (k5.java)
k7 (k7.java)
직역해보면 autowire할 수 없다. 'Car' type의 bean이 한개보다 많다. 라고 한다.
그렇다. 구현체가 두개 이상 있는 경우에는 역시나 DI를 제대로 하지 못한다.
1번째 방법을 통해 해결해본 코드이다.
@Autowired
private Car k5;
결과적으로 제대로 DI를 통해 객체를 주입받지만, SOLID의 원칙인 OCP와 DIP가 위배된 코드를 확인할 수 있다.
2번째 방법을 통해 해결해본 코드이다.
@Qualifier("k5")
@Autowired
private Car car;
결과적으로 제대로 DI를 통해 객체를 주입받고, OCP도 지키며 DIP도 지킨다고 볼 수 있다. Spring에서도 @Qualifier를 추가하여 문제를 해결하라고 안내하긴 한다. 하지만,, @Qualifier("k5")를 사용하다가 후에 k7으로 주입하고 싶으면 @Qualifier("k5") -> @Qualifier("k7") 변경 작업이 불가피하기때문에 결과적으로 보면 OCP와 DIP를 위반한다고 볼 수 있다.
3번째 방법을 통해 해결해본 코드이다.
@Component
@Primary
public class k5 implements Car{
@Override
public int go() {
return 5;
}
}
해당 @Primary의 경우 구현체 중 우선순위를 정해준 것이기 때문에, 같은 interface의 구현체 중 1개만 정해야 하는 단점이 있지만, 테스트 클래스에서 수정하는 것이 아닌 구현체 클래스에서 수정하는 것을 볼 수 있다. 또한, OCP와 DIP도 지켜주는 모습이다.
4번째 방법을 통해 해결하는 방법은 사용하는 구현체 딱 한 개 말고는 @Component를 삭제하여 Bean에 등록을 안시켜주면 된다. Spring에서 Interface에 의존성 주입을 할 때 1개의 구현체만 있는경우는 문제가 생기지 않기 떄문이다.
결과적으로, 3번째와 4번째 방법이 좋은 해결 방법이라고 볼 수 있다.
가장 좋은 방법은 4번째 방법으로, 사용할 하나의 구현체만 Bean으로 등록하는 것이 좋은 방법인 것 같다.
하지만, 여러개의 구현체를 두고 상황에 맞춰서 각각 다른 구현체를 사용해야 한다면 2번째 방법을 통해 해결하는 것이 최선인 것 같다.
혹시나, 나중에 구현체를 두개이상 두는 경우 해당 방법으로 해결하면 좋을 것 같다.
XML
-> XML 편의 기능 지원
-> 자바 코드
-> 자바 8
-> 스프링 부트 1.0
-> 스프링 프레임워크 5.0, 스프링 부트 2.0 (비동기 논블로킹 가능)
-> 스프링 프레임워크 5.2.x, 스프링 부트 2.3.x
프로그램을 객체들의 모임으로 파악하고자 하는 것
객체는 메시지를 주고받고 데이터를 주고받을 수 있다.
다형성: 유연하고 변경이 용이
키보드, 마우스 부품 갈아끼우듯이
자동차를 바꿔도 운전자는 새로운 면허를 안따도 계속 운전 가능
정렬 알고리즘
역할과 구현을 분리
좋은 객체 지향 설계의 5가지 원칙
SRP: 단일 책임 원칙
OCP: 개방-폐쇄 원칙
확장에는 열려있으나 변경에는 닫혀있어야 함
안좋은 예: 인터페이스를 구현한 객체를 새로 추가하고 사용하는 클라이언트에서 새로 구현한 애로 직접 바꾸는 것
-> 이걸 연관관계를 맺어주는 별도의 조립, 설정자가 필요하고. 이런 해주는 게 스프링
LSP: 리스코프 치환 원칙
ISP: 인터페이스 분리 원칙
DIP: 의존관계 역전 원칙
Q. 다형성을 설명할 때 인터페이스 + 오버라이딩으로 설명하는데 다형성이라는 장점을 강조하려면 인터페이스를 많이 사용하면 좋을 것 같은데 왜 실무에서는 인터페이스를 막상 잘 활용하지 않을까? 왜 불편할까? -> 이건 강의에도 나왔듯이 실제 구현체가 어딘지 찾는 단계가 하나 더 추가돼서 나중에 유지보수할 때 귀찮고 시간이 많이 들어가기 때문인 것 같다. 그래서 확장 가능성이 없으면 직접 구현한 클래스를 쓰라는 것...
Q. 대세는 객체 지향이 아닌 함수형이라는 소리도 있는데 둘의 장단점? 어떤 분야의 서비스를 개발할 때는 함수형이 더 편리할까?
Q. SOLID 원칙을 잘 지켜서 개발한 경험 / 원칙을 잘 지키지 못한 경험
클린 코드의 저자 로버트 C. 마틴이 정의한 좋은 객체 지향 설계의 5가지 원칙
- SRP(Single Responsibility Principle): 한 객체는 하나의 책임만을 가진다.
어떤 클래스를 변경해야 하는 이유는 오직 하나뿐이어야 한다. - 로버트 C.마틴
- 하나의 책임을 가진다? == 한 객체를 변경했을 때, 다른 객체들을 변경할 필요가 없다.
- ex) 객체의 생성/사용을 분리한다.
- OCP(Open/Closed Principle): 소프트웨어 요소는 확장에는 열려있지만, 변경에는 닫혀있어야 한다.
- 다형성을 활용해 역할과 구현을 분리하면 지킬 수 있다!
- 인터페이스(역할)의 새로운 구현 클래스(구현)을 만들면 기능을 확장할 수 있다. 또한 이를 확장한다고 했어 인터페이스에 의존하고 있는 다른 코드들을 변경할 필요는 없다.
- LSV(Liscokv Substitution Principle): 다형성에서 하위 클래스는 인터페이스 규약을 다 지켜야 한다
서브 타입은 언제나 자신의 기반 타입으로 교체할 수 있어야 한다. - 로버트 C.마틴
- 인터페이스에 정의한 규약(문법 뿐만 아니라 기능에 대한 암묵적 정의)를 구현체가 모두 따라야지 믿고 구현체를 변경할 수 있다.
- ISP(Interface Segregation Principle): 하나의 범용적인 인터페이스보다 특정 클라이언트를 위한 여러개의 인터페이스가 낫다.
클라이언트는 자신이 사용하지 않는 메소드에 의존 관계를 맺으면 안 된다. - 로버트 C.마틴
- 인터페이스를 분리하면, 인터페이스가 명확해지고 대체 가능성도 높아진다.
- DIP(Dependency Injection Principle): 고차원 모듈은 저차원 모듈에 의존하면 안 된다.
- 고차원 모듈 == 추상화 == 역할 == 인터페이스
- 저차원 모듈 == 구체화 == 구현 == 구현 클래스
- 클라이언트가 인터페이스에 의존해야 유연하게 구현체를 변경할 수 있다.
// DIP : 구체화와 추상화에 모두 의존하고 있다.
// OCP : 구체화를 변경할 때 클라이언트의 변경이 필요하다.
public class MemberService(){
MemberRepository memberRepository = new MemoryMemberRepository();
// MemberRepository memberRepository = new JdbcMemberRepository();
}
스프링 프레임워크 / 객체지향
객체지향
스프링은 객체 지향 언어가 가진 강력한 특징을 살려내는 프레임워크
스프링은 다형성을 극대화해서 이용할 수 있게 도와준다.
스프링 프레임워크의 제어의 역전(IoC), 의존관계 주입(DI), 다형성을 활용해서 역할과 구현을 편리하게 다룰 수 있도록 지원한다
다형성 만으로는 OCP, DIP를 지킬 수 없는데, 프레임워크가 가능하게 해준다
원칙
SRP: 단일 책임 원칙(single responsibility principle)
OCP: 개방-폐쇄 원칙 (Open/closed principle)
LSP: 리스코프 치환 원칙 (Liskov substitution principle)
ISP: 인터페이스 분리 원칙 (Interface segregation principle)
DIP: 의존관계 역전 원칙 (Dependency inversion principle)
어떻게 원칙을 지킬지?
이 문제를 해결하려면 누군가 클라이언트인 OrderServiceImpl에 DiscountPolicy의 구현 객체를 대신 생성하고 주입해주어야한다
공연 기획자가 배역에 맞는 배우를 섭외하고, 역할에 맞는 배우를 지정하는 책임을 담당하는 것처럼, 배우와 공연 기획자의 책임을 확실히 분리할 필요가 있음.
객체의 생성과 연결은 AppConfig가 담당
제어의 역전
AppConfig가 등장한 이후에 구현 객체는 자신의 로직을 실행하는 역할만 담당한다. 프로그램의 제어 흐름은 이제 AppConfig가 가져간다.
AppConfig 처럼 객체를 생성하고 관리하면서 의존관계를 연결해 주는 것을 IoC 컨테이너 또는 DI 컨테이너라 한다.
싱글톤 스프링 컨테이너
스프링 컨테이너를 사용하면, 기본적으로 객체를 다 싱글톤으로 만들어서 관리해줌. 컨테이너는 객체를 하나만 생성해서 관리한다.
스프링 컨테이너의 이런 기능 덕분에 싱글턴 패턴의 모든 단점을 해결하면서 객체를 싱글톤으로 유지할 수 있다.
xxxxCGLIB
생성된 CGLIB 임의의 다른 클래스가 싱글톤이 보장되도록 해준다. 아마도 바이트 코드를 조작해서 작성되었을 것