관리 메뉴

한다 공부

[Spring] 스프링 프레임워크와 DI (Dependency Injection) 본문

Dev/Java

[Spring] 스프링 프레임워크와 DI (Dependency Injection)

사과당근 2024. 4. 10. 22:30

최근에 스프링을 다시 공부할 일이 생겨서
공부하면서 배운 것을 요약 정리해봤다..
아직 갈길이 멀구나..
 

 

~ 스프링의 탄생 ~

예전에는 EJB라는 것을 사용해서 개발을 했다.
하지만,
1. 복잡한 스펙으로 많은 학습 시간 필요, 복잡한 유지보수
2. EJB를 실행하기 위한 WAS의 비용 문제 (수천만원 이상..)
3. 메모리를 많이 사용하기 때문에 성능이 좋지 않고, 이를 개선하기 위한 여러 디자인패턴 숙지 필요
등등의 단점으로 EJB를 사용하던 시절은 개발자들에게 겨울이었다고 한다.
그러면서 2005년 개발자들의 요구가 충분히 반영된 오픈소스 프레임워크가 등장하면서 개발자들에게 봄이 찾아왔다.
그래서 이 오픈소스 프레임워크의 이름이 스프링이라고 한다.
 
스프링은 POJO를 사용한다.
POJO란, Plain Old Java Object의 약어로, 평범한 옛날 자바 객체를 의미한다.
POJO를 사용하지 않는 대표적인 예로는 서블릿이 있다.
서블릿은 POJO와 반대로 특수한.. 자바 객체를 사용한다고 생각하면 되는데,
즉 내 마음대로 개발할 수 없고 무조건 특정 규칙에 맞게 개발을 해야하는 것이다.
서블릿의 특정 규칙은 다음과 같다.
1. HttpServlet 상속
2. SerialVersionID 필요
3. default 생성자 필요
4. import jakarta
5. life cycle에 해당하는 callback method override..
너무 규칙이 많다!
반대로 POJO의 특성을 지닌 스프링은, 이러한 규칙이 존재하지 않는다.
 

~ 스프링부트의 탄생 ~ 

스프링을 쓰다보면 iBatis나 Hibernate와 같은 오픈소스 프레임워크를 사용하여 빠르고 쉽게 DB연동을 할 수 있다.
스프링에서도 자체적으로 편리하게 DB연동을 할 수 있는 라이브러리를 개발할 수 있지만,
어느 영문인지 스프링은 iBatis 같은 기능을 개발하기 보다, 연동이 가능하도록 하는 데에 집중을 했다고 한다.
아마도 그게 더 효율적이라고 생각했겠지
 
그런데 이후 Node.js, Ruby 등 웹에 최적화 된 프레임워크들이 탄생하게 되었다,
그러면서 스프링은 위기에 봉착한다.
스프링으로는 거의 웹 개발을 하는데,
아무래도 라이브러리와 서버가 내장된 더 간편하고 쉬운 프레임워크를 사용하게 되지 않겠는가!
다른 웹 프레임워크와 달리, 스프링에서는 여러 기능을 사용하기 위해 점점 xml이 늘어나고, 점점 jar파일이 (라이브러리) 늘어났다..
이 문제를 해결하기 위해 스프링부트가 탄생하였다.
 
스프링부트는 스프링의 라이브러리를 자동으로 관리해주고, xml설정을 대신 해준다.
이 두가지 특징을 제외하고는, 스프링과 스프링부트는 같다고 봐도 된다.
 


~ IoC ~ 

스프링 공부를 하면 꼭 나오는 IoC.
IoC는, 제어의 역전으로 Inversion of Control의 약자이다.
보통 객체지향 개발을 하다보면 내가 new 를 사용해서 객체를 생성한다.
그런데 IoC, 즉 역제어를 사용하다보면 컨테이너가 객체를 생성해서 실행을 해준다.
 
컨테이너는, 맵과 비슷한 구조에 객체를 넣어서 생성하고 관리하는 것이라고 생각하면 된다.
서블릿 컨테이너를 예로 들자면, 서블릿 컨테이너는 xml을 보고 관련된 클래스의 객체를 자동으로 생성하고 실행한다.
 
스프링에서도, 스프링 컨테이너를 관리하기 위해서 xml파일이 필요하다.
자바 객체를 스프링에서는 빈이라고 하는데, 컨테이너는 빈을 관리한다.
빈 하나당 객체 하나가 메모리에 뜨는 것이다.
xml에 <bean></bean> 태그를 써서 객체를 관리할 수 있는 것이다
 

~ Pre Loading ~ 

서블릿의 경우, index.do 등 -.do를 브라우저에서 실행시켜야 로딩이 되는 구조를 가진다.
즉, 요청이 올 때까지 객체 생성을 지연시키는 것이다.
이를 lazy loading이라고 한다.
 
반대로 pre loading의 경우, 컨테이너가 구동될 때 xml파일에 등록된 객체가 메모리에 로딩된다.
장점으로는, 이미 객체가 메모리에 올라가 있으므로 속도가 빠르다는 점이 있다
하지만, 자주 사용되지 않는 객체들은 메모리를 차지하여 메모리 낭비가 발생할 수 있다는 단점이 있다.


~ DI ~ 

스프링은 IoC를 두가지 형태로 지원한다.
1. Dependency Lookup :
잘 안쓰인다고는 하는데.. getBean등을 통해 스프링 컨테이너가 생성한 객체를 외부에서 검색하는 것이다.
2. Dependency Injection :
객체와 객체 사이의 의존관계를, xml파일을 기반으로 컨테이너가 자동 처리하는 것이다.
예를 들어 Car객체의 구현체로 SUV가 있고 VAN이 있다고 해보자.
이 때, 내가 사용하고 있는 SUV객체를 VAN 객체로 바꾸려고 해도
DI를 잘 적용한다면 이러한 의존 관계를 자바 코드를 수정해서 바꾸는게 아니라
메타 데이터만 수정해서 변경할 수 있게 된다.
 
DI에서 의존관계를 처리할 때, 생성자를 기반으로 하는 Injection이 있고,  Setter를 사용한 Injection이 있다.
 
생성자 인젝션은.. 쉽게 말해 객체 안에 생성자 만들어서 this.data = data; 써서 주입하는 것이다.
그리고 xml에 <constructor-arg ref="생성자의 인자로 전달할 data의 id"/> 를 추가해서
어떤 빈에 어떤 객체를 생성자 주입할건지 명시해주면 된다.
참고로, 인자로 전달할 데이터가 다른 객체의 참조 정보일 때는 ref="" 를 통해 전달하면 되고,
고정된 문자열이나 기본형 데이터를 전달하고자 하면 value=""를 사용하면 된다.
 
Setter 인젝션은 set 함수를 써서 주입하는 것이다.

public void setData(Data data){
	this.data = data;
}

Setter 메소드가 호출되는 시점은, <bean> 객체 생성 직후이다.
그래서 기본 생성자도 만들어둬야한다ㅜ
xml에는 <property name="data" ref="data"/> 를 추가하면 된다.
name 값에는, 세터 메소드의 이름에서 set을 빼고 앞문자를 소문자로 바꾼 것을 넣어야 한다.
 
아니 setter 주입이랑 생성자 주입이랑 거기서 거기 같아 보이는데 무슨 차이가 있는 것이오!
싶지만, 조금 차이가 있다.
생성자 주입을 할 경우, 내가 매개변수를 넣고 싶은 만큼 생성자를 만들어야한다.
즉, 매개변수가 1개인 생성자.. 2개인 생성자... n개인 생성자 등등 원하는 만큼 오버로딩하는 것이다.
하지만 setter를 사용할 경우 Set 메소드 하나로 가능하므로 더 편리하다.
 

~ Collection Injection ~ 

List, Set ,Map, Properties 등 컬렉션 객체를 의존성 주입해야하는 경우도 생긴다.
xml 설정을 통해 이들도 주입할 수 있다!
Properties의 경우, 맵과 비슷한건데 숫자와 문자열만 저장할 수 있는 것이다.
보통 Properties와 List를 주로 사용한다고 한다.
xml에 <list>, <props> 태그를 사용하면 된다.
 

~ Annotation ~ 

앞서 이야기한 것에서 볼 수 있듯이, xml에서 자꾸 뭘 설정해야한다.
그런데 어노테이션을 쓰면 xml설정을 최소화시킬 수 있다.
 
어노테이션은 쉽게, 내장 함수와 같은 기능을 한다고 생각하면 된다.
어노테이션을 사용하면서 특정 기능을 수행할 수 있으며,
여러 어노테이션을 묶어서 하나의 어노테이션을 만들 수도 있다.
 
우선, xml에 <bean>을 등록하지 않고, 컨테이너가 자동으로 객체를 생성하게 하려고 하면,
xml에 <context:component-scan ... > 설정을 해두면 된다.
그리고 클래스에 @Component를 붙이면 된다.
그러면 @Component 어노테이션이 붙은 클래스를 스캔하여, 자동으로 객체를 생성한다.
@Component("아이디")를 통해 아이디를 지정할 수도 있다.
 
의존성 주입을 하려면 @Autowire를 사용하면 된다.
그러면 생성자, Setter, Xml 설정 없이 의존성 주입을 할 수 있다.
이 어노테이션은 생성자, 메소드, 멤버변수 위에 사용할 수 있다.
하지만 대부분 멤버변수 위에 선언하여 사용한다고 한다.
변수 위에 설정하면, 해당 타입의 객체를 메모리에서 찾아 자동으로 할당한다.
 
@Resource는 @Autowire과 @Qualifier (특정 이름의 객체를 주입할 때 사용하는 어노테이션)을 합친 것이다.
전자정부표준 프레임워크에서는 @Resource를 사용한다.
 
즉,
@Autowire
@Qualifier("a")
를 
@Resource(name="a") 로 처리할 수 있는 것이다.
 
그러면 언제 빈 등록하고 언제 컴포넌트 스캔을 쓰는데욧!!!!
빈 등록은, 객체가 자주 변경될 때 사용하면 되고
@Component는 객체에 변경이 없을 때 사용하면 된다.
그도 그럴것이, 컴포넌트 스캔을 쓸 때 객체 변경을 하려면 자바 코드 수정이 들어가니께..
(우리의 목표는 자바 코드 변경 없이 객체 변경하깃)


~ 비즈니스 컴포넌트 개발 ~ 

일반적으로 프로젝트를 할 때에는 네 개의 자바 파일로 비즈니스 컴포넌트를 구성한다.
1. VO (Value Object)
2. DAO (Data Access Object)
3. Service 인터페이스
4. Service 구현 클래스
 
1. VO (Value Object)
- VO 클래스는 DB에 생성된 테이블의 데이터를 매핑하기 위해 사용한다.
대략적으로 VO 클래스는 다음과 같이 구성된다.

public class VO {
	private int id;
    
    public int getId(){
    	return id;
    }
    public int setId(int id){
    	this.id = id;
    }
}

롬복 쓰면 Getter Setter 따로 안 만들어도 됨 > <
(롬복 쓰면 @Getter, @Setter 어노테이션으로 설정 가능!)
VO에 toString 메소드 만들어서 VO 객체 값을 편하게 출력할 수도 있다.
 
2. DAO (Data Access Object)
- DB연동을 담당하는 클래스이다.
그래서 DAO에는 CRUD 기능의 메소드가 포함되어 있다. (참고로 CRUD는 create, read, update, delete)
 
3. Service 인터페이스
- 사실 인터페이스는 안 만들어도 된다.
근데 클라이언트의 수정이 잦고, 구현체를 여러개 만들어 두고 변경을 해야하는 경우가 많다면인터페이스가  필요하다..
 
4. Service 구현 클래스
- 오버라이딩을 (implements Service) 통해 ServiceImpl 클래스를 구현하는 것이 일반적이다.
 

~ @Service @Repository @Controller ~ 

앞에서 @Component를 사용해서 객체를 생성하고 관리했다.
근데 @Component를 쓰면 어떤 클래스가 어떤 역할을 수행 중인지 알기 힘들다.
그래서 스프링은 @Service @Repository @Controller를 제공한다. (어찌나 친절한지!)
 
ServiceImpl에 @Service를,
DAO에 @Repository를, 
Controller에 @Controller를 붙이면 된다.
근데 각각의 어노테이션에 @Component가 포함되어 있어서,
Service에 @Repository를 붙이는 만행을 저질러도 실행은 된다.