목차
- 스프링 프레임워크(Spring Framework)
- 스프링 삼각형
2-1. IoC Container(DI)
2-2. AOP(Aspect Oriented Programming)
2-3. PSA(Portable Service Abstraction) - 스프링 구성 모듈
3-1. Core Container
3-2. Data 접근 계층
- 예제A. BeanFactory(XML Config)
A-1. POJO
A-2. spring-context.xml
A-3. Application.class GenericXmlApplicationContext - 예제B. BeanFactory(Java Config)
B-1. POJO
B-2. ContextConfiguration.class
B-3. Application.class AnnotationConfigApplicationContext - 예제CA. ComponentScan(Java Config)
CA-1. POJOs
CA-2. ContextConfiguration.class @ComponentScan, basePackages
CA-3. Application.class getBeanDefinitionNames() - 예제CB. ComponentScan(Java Config)
CB-1. POJOs
CB-2. ContextConfiguration.class excludeFilters
CB-3. Application.class - 예제CC. ComponentnScan(Java Config)
CC-1. POJOs
CC-2. ContextConfiguration.class includeFilters, useDefaultFilters
CC-3. Application.class - 예제DA. Component-Scan(XML Config)
DA-1. POJOs
DA-2. spring-context.xml context 스키마 추가, <context:component-scan>, base-package
DA-3. Application.class - 예제DB. Component-Scan(XML Config)
DB-1. POJOs
DB-2. spring-context.xml <context:exclude-filter>
DB-3. Application.class
1. 스프링 프레임워크(Spring Framework)
❗ Spring Framework: The Origins of a Project and a Name
겨울이 지나고 새 시작처럼 찾아온 봄날을 의미하는 스프링(spring)처럼, 스프링 프레임워크는 개발자들이 보다 편하게 개발할 수 있는 환경을 제공해 생산성 향상을 도모한다. 산뜻한 이름과는 달리 그 개념과 범위를 마스터한다는 개념으로 접근할 수는 없는 존재이다. 주요 모듈들을 차근차근 익혀간다는 자세를 갖는 것이 좋다.
- 간단히 스프링(Spring)이라고 불리며, 자바(Java) 플랫폼 위한 오픈 소스 애플리케이션 프레임워크이다.
- 동적 웹 사이트를 개발하기 위한 여러가지 서비스를 제공하고 있고, 대한민국 공공기관의 웹 서비스 개발 시 사용이 권장되는 전자정부 표준프레임워크 기반 기술이다. 달리 말해 강제화되어 있는 측면에서 국내 수요 또한 높은 편이다.
2. 스프링 삼각형
2-1. IoC Container(DI)
❗ 라이브러리와 프레임워크의 차이
라이브러리(library)의 경우 자신이 만드는 프로그램 안에서 Gson gson = new Gson(); 선언하듯 원하는 곳에 가져다 써야 한다.
반면, 프레임워크(framework)는 톰캣(Tomcat)을 예로 들면 서블릿 전부 WAS 서버 안의 서블릿 컨테이너에서 관리되고 있어new MemberLoginServlet();처럼 작성하지 않고도 사용 가능하다. 톰캣 서버가 구동될 때 init → doGet/doPost → destroy가 발생하고, 개발자가 별도로 호출하지 않고도 서블릿 컨테이너 안에서 지정된 행위가 치러지기 때문이다.
- IoC(Inversion of Control)는 제어의 역전, 제어의 역행을 의미한다.
- 프로그램 구동에 필요한 객체의 생성, 초기화, 사용, 소멸 등에 이르는 생명주기 포함 관리 자체를 개발자가 아닌 컨테이너에서 직접 행하는 것이다.
IoC 컨테이너 역할
❗ 의존성 주입(DI, Dependency Injection)
B 클래스가 A 클래스를 필드로 사용한다고 가정하자. 이들은 의존 관계에 있고, 결합도가 높다고 말할 수 있다. 만일 A 클래스명이 AA로 바뀌거든 B 클래스의 필드명은 오류로 이어져 별도 수정이 요구된다. 이러한 경우에 대비하여 스프링 프레임워크(Spring Framework)에서는 필드로 사용되는 등의 의존 관계를 직접적인 코드 사용 없이 일종의 표시로 나타낸다. 즉 직접적 결합 관계를 낮춤으로써 유지보수성 향상까지 만드는 것이다.
- ①. 객체의 생명주기와 의존성을 관리한다. IoC Container 통해 의존성 주입(DI, Dependency Injection)이 행해진다.
- ②. VO(DTO/POJO) 객체의 생성, 초기화, 소멸 등 생명주기 처리를 담당한다.
- ③. 개발자가 직접 객체를 생성할 수 있지만, 해당 권한을 컨테이너에 위임함으로써 소스 코드 구현 시간 단축이 가능하다.
IoC 컨테이너와 Bean 객체
빈 (Bean) |
스프링이 IoC 방식으로 관리하는 클래스 스프링이 직접 생성과 제어를 담당하는 객체 |
빈 팩토리 (BeanFactory) |
스프링의 IoC를 담당하는 핵심 컨테이너 Bean을 등록, 생성, 조회, 반환하는 기능을 담당 |
애플리케이션 컨텍스트 (ApplicationContext) |
BeanFactory를 확장한 IoC 컨테이너 Bean을 등록하고 관리하는 기능은 BeanFactory와 동일하지만, 스프링이 제공하는 각종 부가 서비스를 추가 제공함 |
설정 메타정보 (Configuration metadata) |
ApplicationContext 또는 BeanFactory가 IoC를 적용하기 위해 사용하는 설정 정보 IoC 컨테이너에 의해 관리되는 Bean 객체를 생성하고 구성할 때 사용됨 |
스프링에서 관리하는 객체를 빈(Bean)이라고 하고, 해당 Bean들을 관리하는 컨테이너를 빈 팩토리(BeanFactory)로 칭한다.
IoC 컨테이너 종류
BeanFactory | 자바 빈 객체를 등록 및 관리 getBean() 메소드가 정의돼 있음 |
ApplicationContext | BeanFactory의 확장 개념 Spring의 각종 부가 서비스 제공 일반적인 IoC 컨테이너를 말함 |
GenericXmlApplicationContext | ApplicationContext를 구현한 클래스 일반적인 XML 형태의 문서를 읽어 컨테이너 역할 수행 |
2-2. AOP(Aspect Oriented Programming)
- 관점 지향 프로그래밍(AOP, Aspect Oriented Programming)을 뜻한다.
- 여기서 관점은 공통 관심사로서, 여러 클래스에서 공통 기능에 해당하는 중복 코드를 매번 반복하는 것보다는 필요한 순간에 불러가 사용하도록 별도 클래스로 분리해놓는 것이 바람직하다.
- 이러한 구조를 통해 응집도를 높이고, 결합도를 낮출 수 있다. 나아가 생산성 향상 및 유지보수성 증가라는 결과로 이어지기도 한다.
2-3. PSA(Portable Service Abstraction)
- 추상화(abstraction)에서 다뤄온 개념처럼, 특정 기술을 숨기는 것을 말한다.
- 추후 사용하게 될 Spring Web MVC와 같이 예를 들어, 내부적으로는 서블릿 기술을 사용하는 모듈이나 해당 기술이 업그레이드돼 다른 버전이 나오는 등 기술 변화가 생기더라도 개발자가 작성한 코드 영역은 별도 수정이 필요치 않도록 만든다.
- 즉 스프링 프레임워크의 동작 코드만 업데이트되면 실질적 사용이 계속되도록 추상화해 제공하는 것이다.
3. 스프링 구성 모듈
이미지 출처 : ① Overview of the Spring Framework 3.0, ② Overview of the Spring Framework 5.0
3-1. Core Container
✅ Core Technologies API
- 스프링(Spring)의 근간이 되는 IoC(DI) 기능을 지원하는 영역이다.
- BeanFactory를 기반으로 Bean 클래스 제어 기능을 지원한다.
이미지 출처 : IoC Container Overview
- IoC 컨테이너의 대표적 역할은 객체생명주기 및 의존성 관리에 있다.
- 이를 위해서는 POJO(Plain Old Java Object)와 설정 메타데이터(Configuration Metadata)가 우선 전달되어야 한다.
❗ POJO(Plain Old Java Object)는 MemberDTO, MemberService 등등 개발자가 사용을 위해 만든 클래스들을 말한다.서블릿(Servlet)은 설계 규약에 따르므로 POJO에 속하지 않는다.
❗ 설정 메타데이터(Configuration Metadata)는 어떻게 동작시킬 것인지에 대한 설정 정보를 담는다. 과거에는 XML 문서로 작성돼 왔으나, 최근에는 순수하게 자바 컨피그(Java Config) 형식으로 쓰이는 편이다.
3-2. Data 접근 계층
- JDBC, 데이터베이스 연결 모듈, 데이터 트랜잭션에 해당하는 기능을 맡는다.
- 영속성 프레임워크의 연결을 담당한다.
예제A. BeanFactory(XML Config)
- BeanFactory란, 최상위 컨테이너로서 ApplicationContext와 함께 스프링 컨테이너(Spring Container)라고 불린다.
- Bean의 생성, 설정, 관리 등의 역할을 맡고 있다.
- 다음은 MemberDTO(POJO, Plain Old Java Object) 클래스와 spring-context(Configuration) XML 설정 정보를 이용해 Bean 등록 및 생성하는 예제이다.
A-1. POJO
private int sequence;
private String id;
private String pwd;
private String name;
- 간단하게 구성된 네 가지 필드에 맞춰 기본 생성자, 매개변수 있는 생성자, 게터와 세터, toString()을 작성한다.
A-2. spring-context.xml
- Spring Bean Configuration File을 생성한다. 이때 파일명은 spring-context로 지정해 주었다.
<?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="member" class="com.reminder.xmlconfig.MemberDTO">
<constructor-arg index="0" value="1"/>
<constructor-arg name="id" value="user01"/>
<constructor-arg index="2"><value>pass01</value></constructor-arg>
<constructor-arg name="name"><value>고길동</value></constructor-arg>
</bean>
</beans>
MemberDTO member = new MemberDTO(1, "user01", "pass01", "고길동");
- MemberDTO를 Bean으로 등록하는 과정이다.
- id 속성을 지정하지 않으면 클래스명의 첫 글자를 소문자로 변환해 자동 지정되는 것이 규칙이다: memberDTO
- DTO 파일에 기본 생성자가 정의되지 않았을 시 매개변수 태그를 포함하지 않은 <bean></bean> 태그는 여기서도 오류로 읽힌다.
- <constructor-arg> 작성법은 위처럼 다양하다. 0부터 시작하는 숫자값으로 구성된 index 속성으로도 개발자가 DTO 파일에 명시한 필드명에 기반한 name 속성으로도 지정 가능하다.
- 한편, <value>의 경우 속성으로 둘 수도 있고, 별도 내부 태그로도 쓰일 수가 있다.
A-3. Application.class GenericXmlApplicationContext
ApplicationContext context = new GenericXmlApplicationContext("com/reminder/xmlconfig/spring-context.xml");
- ApplicationContext의 하위 구현체인 GenericXmlApplicationContext는 XML 설정 메타데이터를 읽어와 BeanFactory를 동작시킨다.
- GenericXmlApplicationContext의 경우 설정 메타정보가 담긴 XML 파일의 클래스패스 하위 경로 전부를 기술해야 찾아 읽어올 수 있다.
MemberDTO member = (MemberDTO) context.getBean("member");
- ①. bean의 id를 이용해 가져오는 방법이다. 이때 id만으로는 타입을 유추할 수 없는 이유로 기본 Object 타입으로 반환된다. 따라서 알맞은 다운캐스팅이 요구된다.
MemberDTO member = context.getBean(MemberDTO.class);
- ②. bean의 클래스 메타정보를 전달하여 가져온다. 가져오려는 bean 타입이 명확하기에
별도 형변환이 필요하지 않다.
MemberDTO member = context.getBean("member", MemberDTO.class);
- ③. bean의 id 및 클래스 메타정보를 함께 명시하는 방법도 있다.
예제B. BeanFactory(Java Config)
B-1. POJO
(예제A에서 사용한 동일 MemberDTO 파일 활용)
B-2. ContextConfiguration.class
@Configuration
public class ContextConfiguration {
- 이 클래스가 설정 메타정보를 가지고 있음을 나타내는 어노테이션(annotation)을 추가한다: @Configuration
- 컨테이너 생성 시 해당 어노테이션이 명시된 클래스를 먼저 인식하고 설정할 것이다.
@Bean(name="member")
public MemberDTO getMember() {
return new MemberDTO(1, "user01", "pass01", "고길동");
}
}
- bean 등록은 클래스 안에 메소드 형식으로 치러진다. 해당 메소드 위에 @Bean 어노테이션을 작성해 bean으로 쓸 수 있다.
- 이때 bean에 대한 별도 이름을 지정하지 않으면 메소드명을 bean의 id로 자동 인식한다: getMember()
- @Bean(name="이름") 또는 @Bean("이름") 형태로 bean의 id 설정이 가능하다.
B-3. Application.class AnnotationConfigApplicationContext
❗ 어노테이션 활용 방식은 AOP 기능이 이용되고 있어 이에 대한 라이브러리 추가가 필요하다.
ApplicationContext context = new AnnotationConfigApplicationContext(ContextConfiguration.class);
- 어노테이션 설정 정보 AnnotationConfigApplicationContext를 읽어 컨테이너로 등록하는 구현체가 사용된다.
- 인자로서 @Configuration 어노테이션을 명시한 설정 클래스의 메타정보를 전달하여 인스턴스를 생성한다.
- 예제A에서와 마찬가지로 ① bean의 id를 이용, ② bean의 클래스 메타정보를 전달, ③ bean의 id 및 클래스 메타정보를 함께 명시하는 방법 통해 bean을 가져올 수 있다.
예제CA. ComponentScan(Java Config) basePackages
- ComponentScan 기능을 이용한 bean 등록에 대한 예시이다.
- 베이스 패키지(base-package)로 설정된 하위 경로에 특정 어노테이션을 가지고 있는 클래스를 찾아 bean으로 등록시킨다.
- @Component 어노테이션이 쓰인 클래스를 인식하여 bean으로 만들게 되며, 그 하위 개념으로서 특수 목적에 따라 세부 기능을 제공하는 @Controller, @Service, @Repository, @Configuration 등을 인식한다.
CA-1. POJOs
-- MemberDTO.class
private int sequence;
private String id;
private String pwd;
private String name;
- 네 가지 필드를 가진 MemberDTO 클래스를 작성한다.
-- MemberDAO.interface
public interface MemberDAO {
/* 회원 번호로 회원 정보를 조회하는 메소드 */
MemberDTO selectMember(int sequence);
/* 회원 정보를 저장하고 결과를 리턴하는 메소드 */
boolean insertMember(MemberDTO newMember);
}
- MemberDAO 인터페이스에 회원 번호 통한 회원 정보 조회용, 회원 정보 저장 및 결과 리턴용 메소드를 정의한다.
- 이처럼 인터페이스로 메소드 사용에 강제성을 부여할 수 있다.
@Component(value="memberDAO")
public class MemberDAOImpl implements MemberDAO {
private final Map<Integer, MemberDTO> memberMap;
public MemberDAOImpl() {
memberMap = new HashMap<>();
memberMap.put(1, new MemberDTO(1, "user01", "pass01", "고길동"));
memberMap.put(2, new MemberDTO(2, "user02", "pass02", "고등어"));
}
/* 매개변수로 전달 받은 회원 번호를 map에서 조회 후 회원 정보를 리턴해주는 용도의 메소드 */
@Override
public MemberDTO selectMember(int sequence) {
return memberMap.get(sequence);
}
/* 매개변수로 전달 받은 회원 정보를 map에 추가하고 성공 실패 여부를 boolean으로 리턴하는 메소드 */
@Override
public boolean insertMember(MemberDTO newMember) {
int before = memberMap.size();
/* key, value 설정 */
memberMap.put(newMember.getSequence(), newMember);
int after = memberMap.size();
/* map의 size가 늘어났다면 추가되었다는 뜻이므로 이렇게 작성 */
return (after > before) ? true : false;
}
}
- 스프링 컨테이너로 하여금 스캐닝 기능 통해 해당 클래스를 bean으로 등록할 수 있도록 하는 어노테이션을 설정한다: @Component
- value 속성 활용하여 bean의 id를 정의할 수 있으며, 이는 생략 가능하다. 생략 시에는 클래스명의 첫 글자를 소문자로 변환해 bean이 명명된다: memberDAOImpl
- @Controller, @Service, @Repository와 동일한 기능을 가지지만, 각 계층을 표현하는 어노테이션은 부가적 혜택을 지녔음에 따라 특정 용도에 맞게 구분하여 사용하는 것이 좋다. 그러나 계층이 명확하지 않은 클래스의 경우에는 보다 상위 개념인 @Component를 쓸 수 있다.
CA-2. ContextConfiguration.class @ComponentScan, basePackages
@Configuration
@ComponentScan(basePackages="com.reminder.javaconfig")
public class ContextConfiguration { }
- javaconfig 하위 config 폴더에 위치해 있기 때문에 베이스 패키지(base-package)에 대한 별도 정의를 필요로 한다.
- 어노테이션을 작성하지 않은 채로 스캔 진행 시 자신이 속한 config 폴더를 베이스(base)로 생각하고 스캔한다.
- @ComponentScan 등록한 뒤에는 지정 폴더를 베이스 삼아 스캔이 진행된다.
- 즉, basePackages 속성 통해 등록되지 않은 패키지는 스캔 대상에서 제외되며, 명시된 패키지 내의 @Component 어노테이션이 탐색된다.
CA-3. Application.class getBeanDefinitionNames()
ApplicationContext context = new AnnotationConfigApplicationContext(ContextConfiguration.class);
String[] beanNames = context.getBeanDefinitionNames();
for(String beanName : beanNames) {
System.out.println("beanName : " + beanName);
}
MemberDAO memberDAO = context.getBean(MemberDAO.class);
System.out.println(memberDAO.selectMember(1));
System.out.println(memberDAO.insertMember(new MemberDTO(3, "user03", "pass03", "새로운멤버")));
System.out.println(memberDAO.selectMember(3));
- getBeanDefinitionNames() 메소드 통해 정의된 bean의 이름들을 불러올 수 있다. 이때 ContextConfiguration 파일에서 베이스 패키지(base-package)로 명시된 경로에 따라 상이한 결과를 낳는다.
- 또, 해당 bean 조회가 정상적으로 치러진다면 관련한 메소드 호출 구문 역시 정상적으로 조회 및 등록 결과를 반환하게 된다.
예제CB. ComponentScan(Java Config) excludeFilters
- excludeFilters 통해 스캐닝에서 제외할 타입을 지정할 수 있다.
CB-1. POJOs
(예제CA에서 사용한 동일 MemberDTO, MemberDAO, MemberDAOImpl 파일 활용)
CB-2. ContextConfiguration.class
@Configuration
@ComponentScan(basePackages="com.reminder.javaconfig",
excludeFilters= {
@ComponentScan.Filter(
/* 1. 타입으로 설정 */
//type=FilterType.ASSIGNABLE_TYPE, classes={MemberDAO.class}
/* 2. 어노테이션 종류로 설정 */
//type=FilterType.ANNOTATION, classes= {org.springframework.stereotype.Component.class}
/* 3. 표현식으로 설정 */
//type=FilterType.REGEX, pattern= {"com.reminder.*"}
)
})
public class ContextConfiguration { }
- 이 클래스가 설정 정보를 담고 있음을 명시하는 @Configuration 어노테이션을 적용한다.
- 다음으로 @ComponentScan 어노테이션의 basePackages 속성 통해 탐색할 경로를 기재한다.
- excludeFilters 속성은 중괄호 {}와 함께 쓰이며, 제외하고자 하는 타입을 지정하도록 돕는다. 작성법으로는 ① 타입으로 설정, ② 어노테이션 종류로 설정, ③ 표현식으로 설정하는 방법들이 있다.
CB-3. Application.class
(예제CA에서 사용한 동일 내용 활용)
- 이때, excludeFilters로 적용한 뒤에는 당연히 해당 bean을 찾을 수 없어 오류로 읽힌다.
예제CC. ComponentScan(Java Config) includeFilters, useDefaultFilters
- useDefaultFilters 속성을 false로 명시하거든 모든 어노테이션 스캔이 진행되지 않는다.
- 이에 더해 includeFilters 속성으로 스캔하고자 하는 대상을 별도 지정하면 그에 대해서만 스캐닝이 이루어진다.
CC-1. POJOs
(예제CA에서 사용한 동일 MemberDTO, MemberDAO, MemberDAOImpl 파일 활용)
CC-2. ContextConfiguration.class
@Configuration
@ComponentScan(basePackages="com.reminder.javaconfig",
useDefaultFilters=false,
includeFilters= {@ComponentScan.Filter(
/* excludeFilters 설정 방식과 동일 */
type=FilterType.ASSIGNABLE_TYPE,
classes= {MemberDAO.class}
)})
public class ContextConfiguration {
- 모든 어노테이션 스캔이 진행되지 않도록 useDefaultFilters를 false로 놓은 뒤, 스캔할 대상을 includeFilters 속성과 함께 명시한다.
- excludeFilters와 동일하게 ① 타입으로 설정, ② 어노테이션 종류로 설정, ③ 표현식으로 설정할 수 있다.
CC-3. Application.class
(예제CA에서 사용한 동일 내용 활용)
예제DA. Component-Scan(XML Config) <context:component-scan>
DA-1. POJOs
(예제CA에서 사용한 동일 MemberDTO, MemberDAO, MemberDAOImpl 파일 활용)
DA-2. spring-context.xml context 스키마 추가, <context:component-scan>, base-package
- context 스키마(schema) 사용 위해서는 Namespaces 탭에서 이를 추가하는 작업이 선행되어야 한다.
<?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-4.3.xsd">
<context:component-scan base-package="com.reminder.xmlconfig"/>
</beans>
- 선택한 뒤에는 XML 파일의 상단에도 해당 스키마와 관련된 선언문이 적용됐음을 확인할 수 있다.
- <context:component-scan> 태그와 base-package 속성 통해 스캐닝을 치를 베이스 패키지를 별도로 지정한다. 그렇지 않은 경우는 자신이 속한 config 폴더 내에서만 스캔을 그칠 것이다.
DA-3. Application.class
(예제CA에서 사용한 동일 내용 활용)
ApplicationContext context = new GenericXmlApplicationContext("com/reminder/xmlconfig/config/spring-context.xml");
- 단, GenericXmlApplicationContext 구현체가 쓰인다.
작업 환경에서 복사/붙여넣기 작업이 계속될 때는 자동 import 구문에 유의해 현재 작성 경로가 맞는지 확인할 필요가 있다.
예제DB. Component-Scan(XML Config) <context:exclude-filter>
DB-1. POJOs
(예제CA에서 사용한 동일 MemberDTO, MemberDAO, MemberDAOImpl 파일 활용)
DB-2. spring-context.xml <context:exclude-filter>
<?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-4.3.xsd">
<context:component-scan base-package="com.reminder.xmlconfig">
<context:exclude-filter type="assignable" expression="com.reminder.xmlconfig.MemberDAO"/>
</context:component-scan>
</beans>
- <context:component-scan> 내부에 <context:exclude-filter> 태그를 작성해 스캐닝에서 제외시킬 타입을 명시할 수 있다. 이때 type 속성과 expression 속성을 작성한다.
DB-3. Application.class
(예제DA에서 사용한 동일 내용 활용)