
늘 그냥 사용했던 스프링에 대해 탐구하고, DI를 XML,어노테이션 방식으로 구현해보며 스프링 핵심기술들을 몸소 체험해보려고 한다 .
기존의 JAVA EE는 너무 무겁고, 설정이 복잡했다. 그래서, 대안으로 EJB라는 기술이 등장했다.
EJB
Enterprise 규모의 애플리케이션 개발을 단순화학 위한 기술 스택 , 하지만, 비즈니스 로직을 작성하기 위해 불필요한 환경 설정 코드가 많이 필요해서 배보다 배꼽이 커진다.
샘플 코드
public class TestEjbClient {
public static void main(String[] args) throws NamingException {
Properties properties = new Properties();
properties.setProperty(Context.INITIAL_CONTEXT_FACTORY,
"org.apache.openejb.client.LocalInitialContextFactory");
properties.setProperty(Context.PROVIDER_URL, "ejbd://host:4201");
Context context = new InitialContext(properties);
TestStatelessEjbRemote testStatelessEjbRemote
= (TestStatelessEjbRemote) context.lookup("ejb/TestStatelessEjbRemote");
testStatelessEjbRemote.sayHello("Stackify");
}
}
스프링의 프로토 타입은 기존의 Java EE의 경량급 대안이자, EJB에서의 단점(환경설정의 불편)을 극복하기 위한 목적이였다.
- 스프링은 개발자가 서비스 도메인과 관련된 비즈니스 로직 작성에만 집중할 수 있도록 다양한
부가 기능들을 지원함.
로깅,트랜잭션,보안,인증등은 부가기능이라고 할 수 있다.- POJO 기반의 단순하고 유연한 비즈니스 로직 작성 지향 , 라이브러리에 의존한 코드로 작성하는것보다 순수 자바 객체로 작성할 수 있도록 제공한다.
- POJO, Plain Old Java Object,

- 개발자가 new 연산자를 직접 사용하지 않고, 스프링 컨테이너라고 불리는 별도의 컨테이너를 통해 비즈니스 객체를 대신 생성해서 주입해주는 기능.
- 우리가
Controller,Service등을 new로 객체를 만들지 않는것이 바로 이거다.
관점 지향 프로그래밍이라는 뜻으로, 프로그램 내에서 공통적으로 존재하는 반복적인 로직을 별도의 코드로 분리하는 방법 , 앞서 말한 로깅, 트랜잭션, 보안 등을 분리하여 별도의 코드로 작성하는 기법.
덕분에, 개발자는 비즈니스 로직에 집중할 수 있음
본질적으로는 AOP와 유사하게 어떤 처리를 수행해야 함에 있어서,
별도의 코드를 작성하지 않고도 Annotation과 같이 코드를 추상화시킨 기능을 사용하여
개발자가 비즈니스 로직 작성에 더 집중할 수 있도록 도와주는 일련의 설계 개념
어노테이션 만으로 어떤 처리를 수행하는걸 추상화해주는 기법. 어노테이션만 있는것은 아니다. 아무튼 맥락은 비즈니스 로직 작성에 집중할 수 있도록 도와주는 개념.
애플리케이션의 동작에 필요한 의존성을 보관,관리하는 공간
IOC(제어의 역전), 의존성을 제어하는 방식을 개발자가 아닌 외부에서 주입해주기 때문에, 제어권의 방향이 역전되었다고 표현함.
컨테이너를 사용하면, 어떤 객체가 자신에게 필요한 의존성을 직접 new 연산자를 통해 해결하는것이 아니라, 외부에서 해당 의존성을 전달,주입 받을 수 있는것.
<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="dev.spring.step03_setter_injections.TapeReader">
</bean>
</beans>


<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="dev.spring.domain.TapeReader">
<property name="tape" ref="tape"></property>
</bean>
<bean id="tape" class="dev.spring.domain.Tape">
<property name="name" value="아일랜드"/>
<property name="worked" value="true"/>
</bean>
</beans>

<property>로 value를 집어 넣어 주었다.<property>로 ref="tape"라고 지정해주면, 마찬가지로 setTape 메서드로 Tape의 의존성을 스프링이 주입해준다.DI중 하나인 Setter_Injection을 설정파일을 통해 동작방식을 이해해보았다.
Setter Injection은 의존성이 주입되기전에 사용될 수 있고 Setter 메서드를 통해 의존성을 변경할 수 있기 때문에 불변객체를 만들기 어렵다.
<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="dev.spring.domain.TapeReader">
<constructor-arg name="tape" ref="tape"></constructor-arg>
</bean>
<bean id="tape" class="dev.spring.domain.Tape">
<constructor-arg name="name" value="아일랜드"></constructor-arg>
<constructor-arg name="isWorked" value="true"></constructor-arg>
</bean>
</beans>
package dev.spring.domain;
public class TapeReader {
private Tape tape;
public TapeReader(Tape tape) {
this.tape = tape;
}
public TapeReader() {
}
public void setTape(Tape tape1) {
this.tape = tape;
}
public void test() {
if (tape.isWorked()) {
System.out.println(tape.getName() + " 잘 동작합니다");
} else {
System.out.println(tape.getName() + " 사기당했어요 ㅋㅋ");
}
}
}
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("constructor-config.xml");
TapeReader reader = context.getBean(TapeReader.class);
reader.test();
Tape tape = context.getBean(Tape.class);
reader.test();
}

Tape라는 빈을 컨테이너에서 꺼내기전에 TapeReader를 먼저 꺼내어도TapeReader엔 Tape값이 정상적으로 들어가있다.- 이는 스프링이 의존 관계를 해석하고 자동으로 주입하기 때문이다.
- 스프링에선
생성자 주입이 권장되고 있다. 이유는 세터 DI에서 단점으로 작용했던불변 객체를 만들 수 있기 때문이다.- 의존성을 주입받을 필드에
final키워드를 지정해서 생성자로 초기화 한 이후에 수정할 수 없다.- 불변성을 유지하는 이유는?
- 값의 무분별한 변경을 막고,
동시성 문제에서 안전할 수 있다.
<?xml version="1.0" encoding="UTF-8"?>
<!-- xmlns:context attribute 추가 해당 xml 엘리먼트 참고 링크 https://docs.spring.io/spring-framework/reference/core/beans/annotation-config.html -->
<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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<bean id="tapeReader" class="dev.spring.annotation.domain.TapeReader"></bean>
<bean id="tape" class="dev.spring.annotation.domain.Tape"></bean>
</beans>
public class Tape {
// Value를 사용해 필드에 값을 직접 주입
@Value("아일랜드")
private String name;
@Value("true")
private boolean isWorked;
public class TapeReader {
@Autowired
private Tape tape;
- Autowired는 클래스의 멤버 변수에 Bean을 자동으로 주입하는 방식이다.
- 세터와 생성자없이 필드에 직접 어노테이션을 사용해서 주입해 주었다. 코드가 간결하지만, 불변성의 문제가 있다.
- 순환 참조 문제
- A가 B를 Autowired하고 B가 A를 Autowired하면 스프링 컨테이너가 두 객체를 생성하려다가 순환 참조 오류가 발생한다.
public class TapeReader {
private Tape tape;
@Autowired
public void setTape(Tape tape) {
this.tape = tape;
}
public class TapeReader {
private Tape tape;
@Autowired
public TapeReader(Tape tape) {
this.tape = tape;
}
<context:component-scan base-package="dev.spring.component_scan"/>
// TapeReader 클래스를 스프링의 컴포넌트 스캔 대상이 되도록 지정
// 해당 클래스는 애플리케이션에서 활용할 비즈니스 객체인 빈으로 등록하도록 선언,정의
// 결과적으로 해당 클래스를 스프링 컨테이너가 관리할 하나의 Bean으로 생성, 관리하도록 설정함
@Component
public class TapeReader {
private Tape tape;
public TapeReader(Tape tape) {
this.tape = tape;
}
public void test() {
if (tape.isWorked()) {
System.out.println(tape.getName() + " 잘 동작합니다");
} else {
System.out.println(tape.getName() + " 사기당했어요 ㅋㅋ");
}
}
}
@Component
public class Tape {
private String name;
private boolean isWorked;
// @Autowired
public Tape(@Value("아일랜드") String name, @Value("true") boolean isWorked) {
super();
this.name = name;
this.isWorked = isWorked;
}
public String getName() {
return name;
}
public boolean isWorked() {
return isWorked;
}
}
@Component로 스캔할 컴포넌트를 지정해두면, 스프링 컨테이너에서 자동으로 컴포넌트를 스캐닝해서 빈으로 등록하고 의존성을 주입해준다.public class BeanConfig {
@Bean
public TapeReader tapeReader(Tape tape){
return new TapeReader(tape);
}
@Bean
public Tape tape(){
return new Tape("아일랜드", true);
}
}
new AnnotationConfigApplicationContext(BeanConfig.class).getBean(TapeReader.class).test(); 이런식으로 Config 파일을 인식시켜주면, XML을 사용하지 않고도 빈을 등록하고 의존성을 관리할 수 있다.@ComponentScan(basePackages = "dev.spring.java_config_with_scan.domain")
public class ComponentScanConfig {
}
@Bean 대신 @ComponentScan을 사용해서 BasePackage를 지정해주면 직접 빈을 등록하지 않아도 의존성을 관리할 수 있다.