- 스프링에서 가장 중요하다고 생각되는 세 가지의 개념에 대해 설명한다
IoC/DI - 제어의 역전/의존성 주입
- 프로그래밍에서 의존성이란?
- 의사 코드
- 운전자가 자동차를 생산한다
- 자동차는 내부적으로 타이어를 생산한다
- 자바로 표현
new Car();
Car 객체 생성자에서 new Tire();
- 의존성을 단순하게 정의하면 다음과 같다
- 의존성은 new다
- new를 실행하는 Car와 Tire 사이에서 Car가 Tire에 의존한다
public interface Tire {
String getBrand();
}
public class KoreaTire implements Tire{
public String getBrand() {
return "코리아 타이어";
}
}
public class AmericaTire implements Tire{
public String getBrand() {
return "미국 타이어";
}
}
public class Car {
Tire tire;
public Car() {
tire = new KoreaTire();
}
public String getTireBrand() {
return "장착된 타이어: " + tire.getBrand();
}
}
public class Driver {
public static void main(String[] args) {
Car car = new Car();
System.out.println(car.getTireBrand());
}
}
스프링 없이 의존성 주입 - 생성자를 통한 의존성 주입
- 의사 코드
- 운전자가 타이어를 생산한다
- 운전자가 자동차를 생산하면서 타이어를 장착한다
- 자바로 표현
Tire tire = new KoreaTire();
Car car = new Car(Tire);
- 주입이란?
- 외부에서 장착시키는 작업을 주입이라고 한다
public interface Tire {
String getBrand();
}
public class KoreaTire implements Tire{
public String getBrand() {
return "코리아 타이어";
}
}
public class AmericaTire implements Tire{
public String getBrand() {
return "미국 타이어";
}
}
public class Car {
Tire tire;
**public Car(Tire tire) {
this.tire = tire;
}**
public String getTireBrand() {
return "장착된 타이어: " + tire.getBrand();
}
}
public class Driver {
public static void main(String[] args) {
**Tire tire = new KoreaTire();
Car car = new Car(tire);**
System.out.println(car.getTireBrand());
}
}
- 기존에는 car가 구체적으로 어떤 tire를 생산할지 결정했지만 지금은 tire를 car에 주입한다
- 의존성 주입의 이점
- car는 그저 tire 인터페이스를 구현한 어떤 객체가 들어오기만 하면 정상 작동한다
- 확장성이 좋아진다
- 컴파일이 수월해진다
스프링 없이 의존성 주입하기 - 속성을 통한 의존성 주입
- 의사 코드
- 운전자가 타이어를 생산한다.
- 운전자가 자동차를 생산한다.
- 운전자가 자동차에 타이어를 장착한다.
- 자바로 표현 - 속성 접근자 메서드 사용
Tire tire = new KoreaTire();
Car car = new Car();
car.setTire(tire);
- 생성자를 통한 의존성 주입은 교체가 어렵다는 단점이 있다
- 자동차를 생산할 때 한 번 타이어를 장착하면 타이어를 교체할 방법이 없다
- 그렇기에 교체가 편리하도록 속성을 통해 의존성을 주입한다
public interface Tire {
String getBrand();
}
public class KoreaTire implements Tire{
public String getBrand() {
return "코리아 타이어";
}
}
public class AmericaTire implements Tire{
public String getBrand() {
return "미국 타이어";
}
}
public class Car {
Tire tire;
public Tire getTire() {
return tire;
}
public void setTire(Tire tire) {
this.tire = tire;
}
public String getTireBrand() {
return "장착된 타이어: " + tire.getBrand();
}
}
public class Driver {
public static void main(String[] args) {
Tire tire = new KoreaTire();
Car car = new Car();
car.setTire(tire);
System.out.println(car.getTireBrand());
}
}
스프링을 통한 의존성 주입 - xml 파일 사용
- 의사 코드
- 운전자가 종합 쇼핑몰에서 타이어를 구매한다
- 운전자가 종합 쇼핑몰에서 자동차를 구매한다
- 운전자가 자동차에 타이어를 장착한다
- 자바로 표현 - 속성 메서드 사용
ApplicationContext context = new ClassPathXmlApplicationContext(”practice.xml”, Driver.class);
Tire tire = (Tire)context.getBean(”tire”);
Car car = (Car)context.getBean(”car”);
car.setTire(tire);
- 운전자가 직접 자동차를 생산하지 않고 종합 쇼핑몰을 통해 구매하는 형태로 변경된다
public interface Tire {
String getBrand();
}
public class KoreaTire implements Tire{
public String getBrand() {
return "코리아 타이어";
}
}
public class AmericaTire implements Tire{
public String getBrand() {
return "미국 타이어";
}
}
public class Car {
Tire tire;
public Tire getTire() {
return tire;
}
public void setTire(Tire tire) {
this.tire = tire;
}
public String getTireBrand() {
return "장착된 타이어: " + tire.getBrand();
}
}
public class Driver {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("practice.xml");
Car car = context.getBean("car", Car.class);
Tire tire = context.getBean("tire", Tire.class);
car.setTire(tire);
System.out.println(car.getTireBrand());
}
}
<?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 id="tire" class="practice.KoreaTire"></bean>
<bean id="americaTire" class="practice.AmericaTire"></bean>
<bean id="car" class="practice.Car"></bean>
</beans>
- Driver 클래스와 xml 파일이 추가되었다
- ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("practice.xml"); 이 종합 쇼핑몰에 대한 정보이며
- 종합 쇼핑몰에 입점된 상품에 대한 정보는 xml 파일에 등록되어 있다
- 스프링을 도입하면 재컴파일/재배포 없이 xml 파일만 수정하면 프로그램의 실행 결과를 바꿀 수 있다
스프링을 통한 의존성 주입 - 스프링 설정 파일(xml)에서 속성 주입
- 의사 코드
- 운전자가 종합 쇼핑몰에서 자동차를 구매 요청한다.
- 종합 쇼핑몰은 자동차를 생산한다.
- 종합 쇼핑몰은 타이어를 생산한다.
- 종합 쇼핑몰은 자동차에 타이어를 장착한다.
- 종합 쇼핑몰은 운전자에게 자동차를 전달한다.
- 자바로 표현
ApplicationContext context = new ClassPathXmlApplicationContext(”practice.xml”)
Car car = context.getBean(”car”, Car.class);
<bean id=”KoreaTire" class="practice.KoreaTire"></bean>
<bean id="americaTire" class="practice.AmericaTire"></bean>
<bean id="car" class="practice.Car"></bean>
<property name=”tire” ref=”koreaTire”></property>
</bean>
import org.apache.catalina.core.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Driver {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("practice.xml");
Car car = context.getBean("car", Car.class);
System.out.println(car.getTireBrand());
}
}
<?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 id="koreaTire" class="practice.KoreaTire"></bean>
<bean id="americaTire" class="practice.AmericaTire"></bean>
<bean id="car" class="practice.Car">
<property name="tire" ref="koreaTire"></property>
</bean>
</beans>
- 변경 사항이 생길 경우 xml파일의 property ref 값만 변경하면 된다
스프링을 통한 의존성 주입 - @Autowired를 통한 속성 주입
- 의사 코드
- 운전자가 종합 쇼핑몰에서 자동차를 구매 요청한다.
- 종합 쇼핑몰은 자동차를 생산한다.
- 종합 쇼핑몰은 타이어를 생산한다.
- 종합 쇼핑몰은 자동차에 타이어를 장착한다.
- 종합 쇼핑몰은 운전자에게 자동차를 전달한다.
Tire tire;
public void setTire(Tire tire) {
this.tire = tire;
}
import org.springframework.beans.factory.annotation.Autowired;
@Autowired
Tire tire;
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd">
<context:annotation-config />
<bean id="tire" class="practice.KoreaTire"></bean>
<bean id="americaTire" class="practice.AmericaTire"></bean>
<bean id="car" class="practice.Car"></bean>
</beans>
- @Autowired의 의미
- 스프링 설정 파일을 보고 자동으로 속성의 setter 메서드 역할을 해주겠다는 의미
- 그렇기 때문에 xml 파일의 property 태그가 사라진다
import org.springframework.beans.factory.annotation.Autowired;
public class Car {
@Autowired
Tire tire;
public String getTireBrand() {
return "장착된 타이어: " + tire.getBrand();
}
}
<bean id="tire" class="practice.KoreaTire"></bean>
<bean id="americaTire" class="practice.AmericaTire"></bean>
<bean id="car" class="practice.Car"></bean>
<bean id="koreaTire" class="practice.KoreaTire"></bean>
<bean id="tire" class="practice.AmericaTire"></bean>
<bean id="car" class="practice.Car"></bean>
- 만약 koreaTire를 삭제하고 AmericaTire의 id 속성을 삭제하면 실행이 되는가?
- 실행된다
- 같은 타입을 구현한 클래스가 여러개 있다면 그 때 bean 태그의 id로 구분해서 매칭한다
- 따라서 id와 type 중 type 구현에 우선순위가 있다
스프링을 통한 의존성 주입 - @Resource를 통한 속성 주입
- @Autowired와 동일한 기능을 하는 어노테이션
- 차이점은 @Autowired는 스프링의 어노테이션이고 @Resource는 자바 표준 어노테이션
- @Autowired는 type이 id보다 우선순위지만 @Resource는 반대로 id가 type보다 우선순위이다
스프링을 통한 의존성 주입 - @Autowired vs. @Resource vs. property 태그
- @Autowired와 @Resource를 바꿔서 사용하는 데 크게 차이가 없다
- 하지만 나중에 스프링이 아닌 다른 프레임워크로 교체되는 경우를 대비하면 자바 표준인 @Resource를 쓰는 것이 유리하다
- @Resource는 개발 생산성이 더 낫고 property는 유지보수성이 좋다
- 프로젝트의 규모가 커지면 XML 파일의 규모가 커지기 마련인데 XML 파일도 용도별로 분리할 수 있기에 더더욱 property를 사용하는 것이 편리하다
- 프로젝트 규모와 팀의 성향에 따라 유연하게 사용하면 된다
DI를 마무리하기 전에 마지막으로 언급할 사항
- 의존 관계가 new 라고 단순화했던 부분
- 사실 변수에 값을 할당하는 모든 곳에 의존 관계가 생긴다
- 즉, 대입 연산자(=)에 의해 변수에 값이 할당되는 순간에 의존이 생긴다
- DI는 외부에 있는 의존 대상을 주입하는 것을 말한다. 의존 대상을 구현하고 배치할 때 SOLID와 응집도는 높이고 결합도는 낮추라는 기본 원칙에 충실해야 한다
- 그래야 프로젝트의 구현과 유지보수가 수월해진다
AOP
- Aspect-Oriented Programming, 관점 지향 프로그래밍
- 스프링 DI가 의존성에 대한 주입이라면 스프링 AOP는 로직(code) 주입이라고 할 수 있다
- 로직을 주입할 수 있는 곳은 Around(메서드 전 구역), Before(메서드 시작 직후), After(메서드 종료 직전), AfterReturning(메서드 정상 종료 후), AfterThrowing(메서드에서 예외가 발생 후 종료 후)이다
PSA
- Portable Service Abstraction
- PSA란 환경의 변화와 관계없이 일관된 방식의 기술로의 접근 환경을 제공하는 추상화 구조를 말한다
- 특정 클래스가 추상화된 상위 클래스를 일관되게 바라보며 하위 클래스의 기능을 사용하는 것을 PSA의 기본 개념이다
- 따라서 PSA가 적용된 코드는 개발자의 기존에 작성된 코드를 수정하지 않으면서 확장할 수 있으며, 어느 특정 기술에 특화되어 있지 않는 코드이다
- 추상화 계층을 사용하여 어떤 기술을 내부에 숨기고 개발자에게 편의성을 제공해주는 것을 서비스 추상화라고 한다
- 위 그림은 java 콘솔 애플리케이션에서 클라이언트가 데이터베이스에 연결되기 위해 jdbcConnector를 사용하기 위한 서비스 추상화의 다이어그램이다
- 그림을 살펴보면 DbClient 클래스는 구현체에 직접적으로 연결해서 얻는 것이 아닌 jdbcConnector 인터페이스를 통해 간접적으로 연결되어 Connection 객체를 얻을 수 있다
- 또한 Connection을 얻는 방식은 getConnection()로 다른 구현체와 동일하다
- 즉, 일관된 방식으로 해당 서비스의 기능을 사용할 수 있다
- 애플리케이션에서 특정 서비스를 이용할 때, 서비스의 기능을 접근하는 방식 자체를 일관되게 유지하면서 기술 자체를 유연하게 사용할 수 있도록 하는 것을 PSA(일관된 서비스 추상화)라고 한다