💡 Constructor DI와 Setter로 DI 할 때의 차이
❌ 결합력이 높은 상황(일체형, 생성자로 DI)
/* ------------ Member Variables ------------ */
private DmMenuDao dao; //참조형
/* ------------ Constructor ------------ */
public MenuServiceImp() {
dao = new DmMenuDao();
}
================================================
⭕ 결합력을 낮춘 상황(조립형, setter로 DI)
/* ------------ Member Variables ------------ */
private MenuDao dao; //참조형
public MenuServiceImp(MenuDao dao) {
this.dao = dao;
}
/* ------------ getters/setters ------------ */
public void setDao(MenuDao dao) {
this.dao = dao;
}
출처: 뉴렉처(https://www.youtube.com/@newlec1)
그런데, 결합력을 낮추고자 했더니 setter로 꽂기 위해
부품(Dependency)들을 다 만들어서
DI를 해줘야 하는 공정과정이 생겨버렸다.
이를 해결하기 위해서 우리는 앞으로 Spring을 사용하게 될 것이다.
#SPRING 의 핵심적인 역할
SPRING은 이런 부품조립의 역할을 완벽하게 해결해주었다.
스프링의 핵심적인 역할은 DI처리와 transaction management이다.
출처: 뉴렉처(https://www.youtube.com/@newlec1)
DI(부품을 만들어서 조립)하는 것을
모두 SPRING에게 맡겨보자!
(조립해주는 과정에서 IoC 컨테이너의 개념이 등장하니
이것도 잘 이해해보자)
우리는 이제 Java EE를 대신해서 Spring을 사용할 것이다!
SPRING이 DI를 잘 해결할 수 있게 하기 위해서
우리가 고려할 부분은 참조형을 인터페이스로 설정하는 것 뿐이다.
출처: 뉴렉처(https://www.youtube.com/@newlec1)
참조형을 인터페이스로 하는 것이 무슨 의미일까?
아래 이미지와 같이 사용하려는 DAO의 내부코드가 달라질 때
Service에서는 영향을 받지 않도록 해야 한다.
그렇다면 우리가 DAO를 어떻게 Service에 영향을 끼치지 않고
갈아끼울 수 있을까?
참조형태를 인터페이스로 하여 어떤 객체가 오든
문제가 생기지 않도록 코드를 분리한다.
(B1처럼 클래스명으로 참조하지 않고
B라는 인터페이스명으로 참조!)
출처: 뉴렉처(https://www.youtube.com/@newlec1)
그러면 참조형태에 대한 문제는 해결이 되었다.
그러면 생성할 DAO객체에 대한 문제는 어떻게 처리해야할까?
결국 소스코드를 수정해야 한다면
결합력이 높은 상태라는 뜻인데..?
(new할 객체 B2 부분은 어떻게 처리해야 하나?)
출처: 뉴렉처(https://www.youtube.com/@newlec1)
이제 이 객체 생성 부분을외부 설정 파일로 뺄 예정이다.
바로 SPRING이 우리가 파일을 통해 설정한 내용을 바탕으로
객체를 만들어서 조립해주면서 모든 고민이 싹 다 해결! 된다.
SPRING은 설정파일의 내용대로 객체를 만들어서 가지고 있는데,
(언제든 줄 수 있도록)
그러면 이 객체들을 담기 위한 공간이 필요하다.
소프트웨어에서는 이런 공간을 Container라고 부른다.
#컨테이너
💡 컨테이너(Container)란?
컨테이너는 보통 객체의 생명주기를 관리하고, 생성된 인스턴스들에게 추가적인 기능을 제공하는 것이다.
객체를 저장하기 위한 공간을
소프트웨어에서는 컨테이너라고 하는데,
컨테이너를 명명하는 방법은 간단하다.
서블릿을 가지고 있다면 서블릿 컨테이너,
bean을 가지고 있다면 빈 컨테이너와 같은 식으로
담고 있는 것에 대한 것이 컨테이너의 이름이 된다.
출처: 뉴렉처(https://www.youtube.com/@newlec1)
#IoC(Inversion of Control)컨테이너
스프링 프레임워크도 객체를 생성하고 관리하고
책임지고 의존성을 관리해주는 컨테이너가 있는데, 그것이 바로 IoC 컨테이너(=스프링 컨테이너)이다.
컨테이너는 담고 있는 것을 이름으로 한다고 하였는데,
IoC 컨테이너에서 IoC는 담고 있는 객체를 의미하는 것이 아니다.
IoC 컨테이너란 행위를 의미하는 컨테이너이다.
코드에서 구현체에 대한 부분이 사라지기 때문에
결합력이 현저히 낮아진 코드를 만들 수 있다..!
💡 IoC (Inversion of Control) 컨테이너란?
IoC(제어 반전)이란, 객체의 생성, 생명주기의 관리까지 모든 객체에 대한 제어권의 흐름이 역행되었다는 것을 의미한다. (객체 조립에 대한 방법이 반대로 바뀌었음)
즉 IoC는 흐름에 대한 Inversion이다. (inversion은 결합력을 낮춘다는 뜻으로 이해할 것!) 기존에는 가장 큰 틀에서부터 하나씩 부품을 끼워가면서 A→B→C→D의 흐름으로 흘러갔는데 IoC를 통해서 가장 작은 부품부터 만들고 거꾸로 끼워가면서 진행된다.
인스턴스 생성부터 소멸까지의 인스턴스 생명주기 관리를 개발자가 아닌 IoC컨테이너가 대신 해준다. 객체관리 주체가 프레임워크(Container)가 되기 때문에 개발자는 로직에 집중할 수 있는 장점이 있다.
출처: 뉴렉처(https://www.youtube.com/@newlec1)
#Application Context
IoC Container에는 다양한 종류의 객체가 있는데,
그 중에서도 스프링에서 제공하는
Application Context라는 IoC Container를 구현하는 객체에 대해서
잘 알아보자.
💡 Application Context란?
스프링 프레임워크에서 제공하는 IoC 컨테이너의 구현체 중 하나이다.
이는 IoC 컨테이너의 한 형태로, 스프링 애플리케이션을 관리하고 Bean의 라이프사이클을 관리하며 의존성 주입을 수행한다.
출처: 챗지피티(https://chat.openai.com/)
Application Context 안에서도 여러 종류가 있으니 참고할 것!
(특히 AnnotationConfigApplicationContext는
xml파일 설정이 아닌 어노테이션으로 DI를 처리할 수 있게 하는 것이다.)
출처: 뉴렉처(https://www.youtube.com/@newlec1)
🔎설정 붙이기 전 다시 한 번 정리!
생성자로 클래스 파일 안에서
DAO 객체를 생성해서 서비스 클래스를 구현하면
나중에 DAO가 달라졌을 때
수정하기가 매우 어려워지는 상황이 발생한다.
때문에 클래스 내부가 아닌 외부에서
인터페이스를 참조하는 형식으로(setter로) 결합력을 낮추었는데,
이렇게 될 경우 객체를 만들고 주입하는 DI가 필수적으로 필요했다.
스프링은 이런 역할을 대신해서 우리의 부담을 덜어주었다.
객체 생성부터 조립까지 모든 것을 처리해준다.
이제 우리는 설정만 해주면 되는데 2가지 방법이 있다.
1. xml파일로 설정하기
2. annotation으로 설정하기
#xml파일로 설정하기
일단 xml파일로 DI를 처리하는 연습을 먼저 해보자.
src/main/resources/ 경로에 config.xml설정파일을 만든다.
💡 config.xml파일을 이용해서 Dependency 생성과 조립 설정을 하자!
★주문서
태그를 간이식으로 사용하려면 시작하는 태그 맨 뒤에 슬래시를 넣어주면 됨!
<bean></bean> → <bean 내용~ />
id에는 변수명을, class에는 패키지까지 포함한 클래스명을 적어주면 된다.
<!-- MenuDao dao = new Dm2MenuDao(); -->
<bean id="dao" class="com.newlecture.spring.dao.dm.DmMenuDao"/>
🚨 property name은 setter의 이름이다.
참조형이면 ref를 쓰고 참조할 변수명을 적어주면 되고 -->
값이면 value를 쓰고 값을 적어주면 된다. -->
<!-- MenuServiceImp service = new MenuServiceImp(); -->
<bean id="service" class="com.newlecture.spring.service.MenuServiceImp">
<property name="dao" ref="dao"/> <!-- DI --> //DI를 할 때 name을 주의할 것!(setter이름임)
</bean>
<!-- more bean definitions go here -->
그리고 역순으로 자바 소스코드에 가서 ClassPath를 설정한다.
💡 ApplicationContext로 ClassPath 설정하기
public class App {
public static void main(String[] args) {
//ApplicationContext가 스프링이고
//스프링에게 일을 시키려면 config.xml을 설정해야 한다.
ApplicationContext context = new ClassPathXmlApplicationContext("config.xml");
//객체를 얻어와서 서비스에 담아준다.
MenuService service = context.getBean(MenuService.class);
//편하게 서비스 함수를 사용하면 끝!
//Dependency를 만들고 주입하는 것은 다 xml파일에서 설정한다.
List<Menu> list = service.getList();
System.out.println(list);
}//class ends
#SPRING Annotation 종류
Annotaion으로 설정하기 전에
종류에 대해서 간략하게 알아보자.
💡Spring 주석
일반적으로 객체를 생성할 때는 Component라고 주석을 달면 된다. @Component
HTML 섹션태그가 의미에 따라 nav, article, aside와 같이 특화시켜 쓸 수 있는 것처럼 객체가 특화된 의미를(역할을) 가지고 있다면 같은 Component도 다음과 같이 세부적으로 쓸 수 있다. (물론그냥 Component라고 써도 됨) @Service @Repository @Controller @Configuration (외부라이브러리 객체 생성 시 사용) 출처: 뉴렉처(https://www.youtube.com/@newlec1)
장점2. 코드 내용이 달라져도 설정을 다시 안해줘도 된다. 클래스에 직접 붙어있기 때문에 알아서 적용이 된다.
SpringConfig 클래스를 만들어서 어노테이션으로 객체를 생성한다.
모양이 생긴 것은 마치 자바 클래스 안의 메소드와 같지만
이것은 자바코드가 아니라 스프링의 설정이다.
아래 스프링 설정으로 객체를 생성할 수 있으려면
그 위에 class 파일도 객체형태이어야 한다.
따라서 class위에도 @Annotation을 붙이고
그 안에 스프링 설정 위에도 @Annotaion을 붙인다.
💡 SpringConfig 클래스를 만들고 객체 생성 준비를 하자.
//해당 경로의 패키지 안을 스캔하면서
//@Component라고 어노테이션이 붙은 애들을 찾아서 다 객체생성해준다.
//범위를 넓게 할 수록 성능이 안좋을 수 있다.(좁게 하면 한정되고..)
//그래서 @ComponentScans() //Scans를 사용해서 배열형태로 넣어서 처리할 수도 있다.
그러나 보통 ComponentScan을 사용함
@ComponentScan("com.newlecture.spring")
public class SpringConfig {
//아래 Bean이라는 어노테이션을 활용해서
//객체를 하나씩 생성해줄 수도 있다.
//이것은 자바 코드이지만 자바코드가 아닌 것으로 봐야 한다. (★설정이다★)
//이렇게 일일이 만들어서 @Bean이라고 설정하지 않고
//위에 @ComponentScan 어노테이션을 사용한다.
@Bean
public MenuDao dao() {
return new DmMenuDao();
}
}//class ends
#외부라이브러리로 가져온 파일 객체만들기
혹시 외부 라이브러리의 파일을 가지고
객체를 생성해야 하는 경우에는 다음과 같이 처리한다.
(외부 라이브러리여도 손쉽게 설정 가능!)
이때는 @Component라는 Annotation이 아니라
더 특화된 이름인 @Configuration이라는 Annotaion을 사용한다.
(외부 라이브러리를 bean으로 만드는 객체란 뜻!)
💡 외부 라이브러리 객체 만들기
//외부 라이브러리의 경우 내가 직접 소스코드에 접근할 수가 없음
//그래서 소스코드가 없는 녀석을 콩 보따리에 담아달라고 요청할 것임
//@Component //컴포넌트라고 써도 되지만
@Configuration //특화된 이름인 configuration을 쓴다. 외부 라이브러리를 bean으로 만드는 객체란 뜻!
public class BeanConfig {
//객체만들기!
//이름이 꼭 함수이름같다. 그래서 @Bean이라고 별도 표시를 해줘야 한다.
@Bean
public List list() {
return new ArrayList();
}
}//class ends
#xml파일로 Setter 설정하기
객체 생성을 완료했다면 Setter로 부품조립을 해보자.
<property>라는 태그를 이용해서 한다.
name속성은 setter의 이름이다.
ref(참조)속성을 사용하거나 value(값)속성을 사용한다.
💡 xml파일로 부품조립하기(setter)
<!-- id에는 변수명을, class에는 패키지까지 포함한 클래스명을 적어주면 된다. -->
<beans>
★★ 1. MenuDao dao = new Dm2MenuDao();를 xml로 만드는 방법 ★★
<bean id="dao" class="com.newlecture.spring.dao.dm.DmMenuDao"></bean>
★★ 2. MenuServiceImp service = new MenuServiceImp();를 xml로 만드는 방법 ★★
<bean id="service" class="com.newlecture.spring.service.MenuServiceImp">
<!-- property name은 setter의 이름이다. -->
<!-- 참조형이면 ref를 쓰고 참조할 변수명을 적어주면 되고 -->
<!-- 값이면 value를 쓰고 값을 적어주면 된다. -->
★★ DI 할 때 필요한 설정 ★★
<property name="dao" ref="dao"></property>
</bean>
</beans>
#Annotation으로 Setter 설정하기
만들려는 dao객체와 serviceimp 클래스에도
@Component 어노테이션을 달아준다.
그러면 SPRING이 자동으로 스캔하면서
@Component라는 어노테이션이 붙은 클래스파일들을
객체생성 해준다.
dao와 serviceimp 객체가 만들어졌으니
serviceimp 객체와 dao를 서로 연결해줘야 하는데,
연결해주는 것은 serviceimp의 setter위에
@Autowired라는 어노테이션을 다는 것으로 설정한다.
💡 dao 구현체와 service 구현체에 @Component 어노테이션 붙이기
✅ dao
@Component /* ("dao") *///이름으로 getBean할 경우에는 설정해줘야 한다!
public class DmMenuDao implements MenuDao {
@Override
public List<Menu> findAll() {
// TODO Auto-generated method stub
List<Menu> list = new ArrayList<Menu>();
list.add(new Menu(1L, "아메리카노", "Americano", 3000, "coffee.png", null, 0, 1L));
list.add(new Menu(2L, "아이스 아메리카노", "Korano", 3500, "coffee1.png", null, 0, 1L));
return list;
}//findAll ends
@Override
public int count() {
return 12;
}//count ends
}//class ends
=========================================================================
✅ service
@Component /* ("service") */ //이름으로 getBean할 경우에는 설정해줘야 한다!
public class MenuServiceImp implements MenuService {
/* ------------ Member Variables ------------ */
private MenuDao dao; //참조형
/* ------------ Constructor ------------ */
public MenuServiceImp() {
}
/* ------------ Methods ------------ */
@Override
public List<Menu> getList() {
return dao.findAll();
}
/* ------------ getters/setters ------------ */
//setter에 와서 어노테이션으로 Autowired만 해주면 자동으로 조립해준다.
//우리가 외부에서 setter로 꽂아줄 필요 없음
@Autowired
public void setDao(MenuDao dao) {
this.dao = dao;
}
}//class ends
#설정방법(2가지) 모두 적용하기
만약xml파일과 Annotation 둘 다를 설정으로 사용하고 싶다면
아래와 같이 context라는 xml 네임스페이스를 추가로 설정하고
<beans>태그 안에 태그 형태로 내용을 적어준다.
💡xml파일, Annotation 둘 다 설정으로 사용하고 싶다면?
<?xml version="1.0" encoding="UTF-8"?>
<!-- xmlns는 xml의 네임스페이스이다. -->
<!-- xmlns뒤에 :키값을 붙여쓴다. 그래서 이 네임스페이스를 쓰려면 밑에서 context:라고 앞에 접두사를 붙여야 한다. -->
<!-- 그런데 이중에서 bean은 기본 네임스페이스로 설정되었다 그래서 context처럼 앞에 접두사를 붙여 쓸 필요가 없다. -->
<!-- xmlns:context처럼 필요시 추가할 수 있고 이때 내가 원하는 이름(예: context)을 설정할 수 있다. -->
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation=
"
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
">
<!-- ★★★ 주문서 ★★★ -->
<!-- xml파일과 annotation을 같이 사용하고 싶은 경우에 대한 옵션 2가지가 있다. -->
<!-- 1번 -->
<context:component-scan base-package="com.newlecture.spring" />
xml파일 안에 아무것도 설정 안하고 @Component포함 클래스에 붙은 어노테이션 다 읽음,
그래서 패키지범위설정이 필요함 그리고 사실 이렇게 되면 xml파일에 설정하는 것이 무의미함
모든 것이 어노테이션을 통해 설정되니까 클래스에서 @ComponentScan하면 됨
<!-- 2번 -->
<context:annotation-config />
context:라는 접두사를 붙여 써야 한다!!
xml파일 안에 <bean/>이라고 적어준 애들을 객체로 만든 뒤 그 안에 있는 어노테이션만 읽음
즉, 이미 객체를 생성했으니 클래스 파일 내에서
@Component 어노테이션만 읽지 않음, 나머지만 다 읽음
</beans>
클래스 패스를 설정하는 main함수에서는
다음과 같이 설정방법을 필요에 맞게 변경할 수 있다.
annotation으로 할 경우에는 main함수에서 이렇게 설정해준다.
💡 App과 어노테이션으로 만든 service와 dao 객체를 사용해보자!
public class App {
public static void main(String[] args) {
/* ----------- 부품을 직접 만들었었던 지난날 ----------- */
//Dependency - 종속객체 -> 부품
MenuDao dao = new Dm2MenuDao();
MenuServiceImp service = new MenuServiceImp();
service.setDao(dao); //Setter Injection - 주입 -> 결합: DI
/* ----------- Spring 등장!! ----------- */
■■■ ApplicationContext 만드는 방법 2가지 ■■■
★★ xml파일로 ApplicationContext 만들기★★
//ApplicationContext가 스프링이고
//스프링에게 일을 시키려면 config.xml을 설정해야 한다.
ApplicationContext context = new ClassPathXmlApplicationContext("config.xml");
★★ Annotation으로 ApplicationContext 만들기★★
//xml파일에 설정하지 않고 어노테이션으로 설정하려면 매개변수를 클래스로 전달해야 한다.
//어노테이션은 클래스에 직접 붙이는 것이니까 클래스로 매개변수 전달!
//다시 말해서 ApplicationContext가 xml일 때는 매개변수가 xml,
//Annotation일 때는 매개변수가 클래스!
//.class는 모든 클래스가 가지고 있는 속성인데, 이클립스가 자기에 대한 설명서를 가지고 있다.
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
■■■ 객체생성 방법 2가지 ■■■
★★ 1. 이름설정 없어도 가능, .class 덕분에 형변환도 필요 없음 ★★
MenuService service = context.getBean(MenuService.class);
★★ 2. 반드시 이름설정 필요, 형변환 필요 ★★
때문에 같은 자료형의 클래스를 여러개 구분해서 사용할 일이 없어서
얘보다는 위에꺼 씀(dao가 수정되면 새로운 것으로 대체해야지 2개의 dao를 나란히 놓고 쓰지 않으니까)
MenuService service = (MenuService) context.getBean("service");
List<Menu> list = service.getList();
System.out.println(list);
}//main ends
}//class ends
#3가지 DI 방법 (setter, constructor, field)
DI를 할 때는 3가지 방법이 모두 가능하다.
setter, constructor, field. 가장 쉬운 방법은 field이다.
그렇다면 setter나 constructor는 언제 활용할까?
setter나 constructor는 함수이기 때문에
injection이 될 때 별도의 코드블록을 실행시킬 수 있다.
만약 DI를 하는 시점에 무언가를 초기화 하거나
다른 조건처리가 필요하다면
setter나 constructor에서 injection을 해준다.
💡 setter, constructor, field에서 injection을 해보자
/* ------------ Member Variables ------------ */
@Autowired //field injection
private MenuDao dao;
/* ------------ Constructor ------------ */
public MenuServiceImp() {
}
@Autowired // constructor injection
public MenuServiceImp(MenuDao dao) {
this.dao = dao;
}
/* ------------ getters/setters ------------ */
//setter에 와서 어노테이션으로 Autowired만 해주면 자동으로 조립해준다.
//우리가 외부에서 setter로 꽂아줄 필요 없음
@Autowired //setter injection
public void setDao(MenuDao dao) {
this.dao = dao;
}