만약 아래와 같은 코드들이 있다.
// Iphone.java
package test;
public class Iphone {
public Iphone() {
System.out.println("아이폰 객체 생성");
}
public void powerOn() {
System.out.println("아이폰 전원 On");
}
public void powerOff() {
System.out.println("아이폰 전원 Off");
}
}
//GalaxyPhone.java
package test;
public class GalaxyPhone {
public GalaxyPhone() {
System.out.println("갤럭시 객체 생성");
}
public void powerOn() {
System.out.println("갤럭시 전원 On");
}
public void powerOff() {
System.out.println("갤럭시 전원 Off");
}
}
//Client.java
package test;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;
public class Client {
public static void main(String[] args) {
Iphone phone = new Iphone();
phone.powerOn();
phone.powerOff();
}
}
Client를 실행하면 콘솔에 순서대로
- 아이폰 객체 생성
- 아이폰 전원 On
- 아이폰 전원 Off
가 출력이 된다.
만약 핸드폰을 Iphone에서 GalaxyPhone 으로 바뀌게 된다면?
Client 코드에서,
Iphone phone = new Iphone();를
GalaxyPhone phone = new GalasyPhone(); 로 바꿔줘야 한다.
이는 결합도가 높은 프로젝트의 형태이며,
결합도를 낮추기 위해 아래와 같은 방식을 사용할 수 있다.
1. 오버라이딩
[오버로딩]
- 함수명 중복정의 허용
- 메서드 시그니처가 다르면, 함수명이 같아도 된다!
- 상속관계 무관
[오버라이딩]
- 메서드 재정의
- 메서드 시그니쳐가 같아야 함
- 상속관계에서 나타남
>> 오버라이딩을 강제 == 인터페이스 사용
Phone.java : 인터페이스 파일을 만들어준다.
package test;
public interface Phone {
public abstract void powerOn(); // 추상메서드
void powerOff();
}
이제 Iphone / GalaxyPhone .java 파일에서 인터페이스를 사용해 "강제" 해서 사용할 수 있다.
// Iphone.java
package test;
public class Iphone implements Phone {
public Iphone() {
System.out.println("아이폰 객체 생성");
}
@Override
public void powerOn() {
System.out.println("아이폰 전원 On");
}
@Override
public void powerOff() {
System.out.println("아이폰 전원 Off");
}
}
// GalaxyPhone.java
package test;
public class GalaxyPhone implements Phone {
public GalaxyPhone() {
System.out.println("갤럭시 객체 생성");
}
@Override
public void powerOn() {
System.out.println("갤럭시 전원 On" + this.num);
}
@Override
public void powerOff() {
System.out.println("갤럭시 전원 Off");
}
}
// Client.java
package test;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;
public class Client {
public static void main(String[] args) {
Phone phone = new GalaxyPhone();
Phone phone = new Iphone();
phone.powerOn();
phone.powerOff();
factory.close();
}
}
이제 선언을 Phone phone 으로 하고,
사용에 따라 new 뒤에 Iphone 혹은 GalaxyPhone로 선언해 주면 된다.
2. 개발 패턴 사용하기
팩토리 패턴 == 컨테이너 구조의 핵심
>> xx 객체가 필요해. 하고 부탁하면, 해당 객체를 주는 패턴을 의미함
ex) HandlerMapper
Spring에서 팩토리 역할 (객체를 생성해서 가져다주는 역할) == 컨테이너
// Client.java
package test;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;
public class Client {
public static void main(String[] args) {
// 스프링 컨테이너 factory = new 스프링컨테이너();
//AbstractApplicationContext factory = new AbstractApplicationContext();
AbstractApplicationContext factory = new GenericXmlApplicationContext("applicationContext.xml");
// 컨테이너는 동작을 하기 위해 설정 파일(.xml)이 필요함
// 컨테이너를 구동(동작)시키는 코드
// applicationContext 이 이름을 가진 설정파일을 만들어야 함
// Phone phone = factory야(스프링컨테이너야). 폰 객체좀("객체명");
Phone phone = (Phone)factory.getBean("samsung"); // 다운캐스팅
// Bean == 자바객체 == 객체 == POJO
// 객체를 요청하다 == look up
phone.powerOn();
phone.powerOff();
factory.close();
}
}
팩토리 패턴을 사용하여 객체 생성(new)을 컨테이너로 대신 한다.
이는 IOC (제어의 역행) 핵심이고, Spring을 사용하는 이유이다.
AbstractApplicationContext() 메서드는 추상메서드이기 때문에 new를 할 수 없다.
따라서 아무 의미가 없는 실습용 메서드 GenericXmlApplicationContext() 메서드를 사용한다.
이를 사용한 코드는 컨테이너를 동작시키는 코드이다.
getBean은 최상위 클래스인 object 타입이기 때문에 다운캐스팅이 필요하며,
컨테이너는 동작을 하기 위해 설정파일인 .xml 파일이 필요하다.
// applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<!-- 알 수 없는 설정들이 들어가 있는데, 이 설정을 >> "스키마" 라고 함 -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="test.Iphone" id="apple" />
<!-- java코드로 바꾼다면 ==Iphone apple = new Iphone(); 와 동일하다 -->
<bean class="test.GalaxyPhone" id="samsung" />
</beans>
bean을 사용하기 위해 루트(최상위) 엘리먼츠 (요소, 태그) 코드 사용
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean class="test.IPhone" id="apple" /> <bean class="test.GalaxyPhone" id="samsung" />
// 이곳에 bean 코드 작성한다
</beans>
beans 안에 bean이 있는 형태!
이를 실행 키시면 아래와 같은 콘솔이 나온다.
나는 아이폰을 사용하지 않는데 아이폰 객체도 생성이 됐다!
이유는?
Sptring에서는 요청을 samsung으로 했는지, apple으로 했는지 모르고 + 컨테이너에서 2개를 new 하라고 했기 때문!
lazy-loading
스프링 컨테이너는 객체를 즉시 로딩한다 (pre-loading)
위에서 사용하지 않는 객체는 new 하지 않기 위해 lazy-loading (지연로딩) 설정을 할 수 있다.
지연로딩은 실제로 객체를 사용할 때 로딩하는 방식이다!
// .xml
<bean class="test.Iphone" id="apple" lazy-init="true" />
<bean class="test.GalaxyPhone" id="samsung" lazy-init="true" />
lazy-init="true" 라는 설정을 추가해 지연로딩 방식을 설정했다.
// consloe
실제로 실행하지 않은 객체는 생성되지 않는 것을 볼 수 있다.
그러나 Spring에서는 pre-loading가 default 인데,
그 이유는 사용자들이 처음 접속 시 발생되는 로딩은 기다려줘도,
사용 중 발생되는 로딩은 불편함을 호소하기 때문이다!
arguments 설정
스프링 컨테이너에게 요청하는 객체명을 "samsung" 혹은 "apple" 로 설정하지 않고, 아래와 같이 설정할 수 있다.
Phone phone = (Phone)factory.getBean(args[0]);
1) 상단의 Run 버튼 옆 ▼ 클릭 → Run Configurations... 클릭
2) Arguments 탭에서 Program arguments에 값을 넣어줄 수 있다.
// Console (실행 결과)
이렇게 값을 넣어주면 매개변수가 변하지 않고 고정되어 돌아간다.
실무에서는 이렇게 반영해서 프로그램 배포하기도 함!!
init() 함수
보통 멤버변수가 있으면 생성자를 통해 멤버변수 초기화가 된다.
그런데 java 이전의 코드들은 init 함수를 통해 멤버변수를 초기화한다.
// Iphone.java
private int num;
public void initMethod() {
this.num=1234;
System.out.println("생성자 역할(멤버변수 초기화)을 대신하는 init() 함수");
}
이를 호출하기 위해서 init-method를 설정해 준다.
// .xml
<bean class="test.Iphone" id="apple" lazy-init="true" init-method="initMethod"/>
// Console
scope 설정
만약 갤럭시 폰이 powerOn 된다면 랜덤한 정수가 나오게 하고 싶다.
// GalaxyPhone.java
package test;
import java.util.Random;
public class GalaxyPhone implements Phone {
private int num;
public GalaxyPhone() {
this.num = new Random().nextInt(100);
System.out.println("갤럭시 객체 생성");
}
@Override
public void powerOn() {
System.out.println("갤럭시 전원 On " + this.num);
}
@Override
public void powerOff() {
System.out.println("갤럭시 전원 Off");
}
}
// Client.java
Phone p1 = (Phone)factory.getBean("samsung");
Phone p2 = (Phone)factory.getBean("samsung");
Phone p3 = (Phone)factory.getBean("samsung");
p1.powerOn();
p2.powerOn();
p3.powerOn();
// Console
콘솔창을 보면 같은 숫자가 나온 것을 볼 수 있다.
이유 : 객체가 한 번만 생성됐기 때문 (new가 한 번!)
이는 싱글톤 패턴이 유지된다는 뜻!
싱글톤 패턴 == 어떠한 객체가 메모리에 단 하나 (or 필요한 만큼!)만 있다.
여기서 알 수 있는 사실은? Spring 프레임워크 (컨테이너)는 default로 싱글톤 패턴을 유지한다!
만약 핸드폰 1개가 아닌 3개가 필요하다면? scope 설정을 해줄 수 있다!
// .xml
<bean class="test.GalaxyPhone" id="samsung" lazy-init="true" scope="prototype" />
scope="prototype" 설정을 추가해 주면 된다.
// Console
원래는 default가 싱글톤, prototype인 경우는 많지 않음!!
가끔 사용될 수 있지만 웬만하면 싱글톤이라고 함
왜냐하면 객체를 여러 개 사용하는 것은 낭비이기 때문이다.
🍀 결론/정리
- Spring에는 POJO를 대신 관리해 주는 컨테이너가 있다.
- 컨테이너 1개당 : 1개의 .xml (설정파일)
- 프로젝트의 결합도를 낮추기 위해 인터페이스를 사용해 낮추고, 팩토리 패턴을 활용한다.
- Spring 프레임워크는 default로 싱글톤 패턴을 유지한다.
'Spring' 카테고리의 다른 글
[Spring] Spring 내용 정리 (0) | 2024.10.06 |
---|---|
[Spring] 의존성 주입 (0) | 2024.10.06 |
[Spring] 스프링 프레임워크 구조 (1) | 2024.10.02 |
[Spring] Project 만들기 (0) | 2024.10.02 |
[Spring] 이클립스 Spring 설치 (0) | 2024.10.01 |