▶ 의존성 주입과 스프링
- 스프링 프레임워크는 웹이라는 제한적인 용도로만 쓰이는게 아닌 객체지향의 "의존성 주입(Dependency Injection)" 기법을 적용할 수 있는 객체지향 프레임워크임.
- 스프링 프레임워크는 경량(light weight) 프레임워크를 목표로 만들어짐.
- 스프링 프레임워크는 가장 중요한 역할을 하는 라이브러리와 여러 개의 추가적인 라이브러리를 결합하는 형태로 프로젝트 구성함. → 대표적으로 웹 MVC 구현을 쉽게 할 수 있는 'Spring Web MVC'나 JDBC 처리를 쉽게 할 수 있는 'MyBatis'를 연동하는 'mybatis-spring' 같은 라이브러리가 있음.
▷ 의존성 주입 (Dependency Injection)
- 의존성 주입은 어떻게 하면 '객체와 객체 간의 관계를 더 유연하게 유지할 것인가?'에 대한 고민으로 객체의 생성과 관계를 효과적으로 분리할 수 있는 방법에 대한 고민에 의해 생김.
- 의존성이란 하나의 객체가 자신이 해야 하는 일을 하기 위해서 다른 객체의 도움이 필수적인 관계를 의미함.
- 과거에는 의존성을 해결하기 위해 컨트롤러에서 직접 서비스 객체를 생성하거나 하나의 객체만을 생성해서 활용하는 등의 다양한 패턴을 설계해서 적용해왔는데, 스프링은 프레임워크 자체에서 지원함.
- 스프링 프레임워크는 필요한 객체를 찾아서 사용할 수 있도록 XML 설정이나 자바 설정 등을 이용할 수 있음.
> 스프링 라이브러리 추가
- 프로젝트 생성 후 기본적으로 스프링을 구동하는데 필요한 라이브러리들을 추가해 줘야 함. 스프링 프레임워크는 추가하는 jar 파일에 따라서 사용할 수 있는 기능들이 달라짐.
- 메이븐 저장소(Maven Repository)를 통해 찾을 수 있음.
- 여러 라이브러리를 추가할 때는 같은 버전을 이용. (버전이 다르면 제대로 동작하지 않을 수 있음)
dependencies {
...
implementation group: 'org.springframework', name: 'spring-core', version: '6.0.11'
implementation group: 'org.springframework', name: 'spring-context', version: '6.0.11'
implementation group: 'org.springframework', name: 'spring-test', version: '6.0.11'
}
> Lombok 라이브러리 추가
compileOnly 'org.projectlombok:lombok:1.18.24'
annotationProcessor 'org.projectlombok:lombok:1.18.24'
testCompileOnly 'org.projectlombok:lombok:1.18.24'
testAnnotationProcessor 'org.projectlombok:lombok:1.18.24'
↳ 테스트 환경에서도 쓸수 있게 테스트 관련 설정도 추가
> Log4j2 라이브러리 추가
- Lombok을 이용해서 @Log4j2를 쓰기 위해 관련 라이브러리 추가
implementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.17.2'
implementation group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.17.2'
implementation group: 'org.apache.logging.log4j', name: 'log4j-slf4j-impl', version: '2.17.2'
- 라이브러리 추가 후 resources 폴더에 log4j2.xml 추가
<?zml version="1.0" encoding="UTF-8"?>
<configuration status="INFO">
<Appenders>
<Console name="console" target="SYSTEM_OUT">
<PatternLayout charset="UTF-8" patterns="%d{hh:mm:ss} %5p [%c] %m%n"/>
</Console>
</Appenders>
<loggers>
<logger name="org.springframework" level="INFO" additivity="false">
<appender-ref ref="console" />
</logger>
<logger name="org.zerock" level="INFO" additivity="false">
<appender-ref ref="console" />
</logger>
<root level="INFO" additivity="false">
<appenderRef ref="console" />
</root>
</loggers>
</configuration>
> JSTL 라이브러리 추가
- JSP에서 사용할 JSTL 라이브러리 추가
implementation group: 'jstl', name: 'jstl', version: '1.2'
▷ 의존성 주입
⦁ 설정 파일 추가
- 스프링 프레임워크는 자체적으로 객체를 생성하고 관리하면서 필요한 곳으로 객체를 주입하는 역할을 하는데 이를 위해서는 설정 파일이나 어노테이션 등을 이용해야 함.
- 스프링이 관리하는 객체들은 빈(Bean)이라는 이름으로 불리며 프로젝트 내에서 어떤 빈들을 어떻게 관리할 것인지를 설정하는 설정 파일 작성 가능.
- 스프링의 빈 설정은 XML을 이용하거나 별도의 클래스를 이용하는 자바 설정이 가능함.
- 프로젝트의 'WEB-INF' 폴더에서 [NEW → XML Configuration File → Spring Config] 선택. 파일 이름을 root-context.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.wpringframework.org/schema/beans/spring-beans.xsd">
// <bean> 태그를 이용해서 설정 추가
<bean class="org.zerock.springex.sample.SampleDAO"></bean>
<bean class="org.zerock.springex.sample.SampleService"></bean>
</beans>
⦁ 빈 설정 테스트
- 스프링 프로젝트 구성시 상당히 많은 객체를 설정하기 때문에 나중에 한번의 에러가 발생했을때 원인을 찾으려고 하면 어려울 수 있음. 따라서 가능한 개발 단계에서 테스트를 진행하면서 개발하는게 좋음.
@Log4j2
@ExtendWith(SpringExtension.class)
@ContextConfiguration(locations="file:src/main/webapp/WEB-INF/root-context.xml")
public class SampleTests {
@Autowired
private SampleService sampleService;
@Test
public void testService1() {
log.info(sampleService);
Assertions.assertNotNull(sampleService);
}
}
↳ @Autowired는 '만일 해당 타입의 빈(Bean)이 존재하면 여기에 주입해 주기를 원한다'라는 의미의 어노테이션. (= 필드 주입(Field Injection) 방식)
↳ @ExtendWith(SpringExtension.class)는 JUnit5 버전에서 'spring-test'를 이용하기 위한 설정. (JUnit4에서는 @Runwith)
@ContextConfiguration은 스프링의 설정 정보를 로딩하기 위해 사용. (XML로 설정시 locations 속성 이용, java 설정시 classes 속성 이용)
▷ ApplicationContext와 Bean
- 서블릿이 존재하는 공간을 Servlet Context 라고 하는 것처럼, 스프링에서는 Bean이라고 부르는 객체들을 관리하기 위해서 ApplicationContext라는 존재를 활용.
⦁ <context:component-scan>
- @Controller: MVC의 컨트롤러를 위한 어노테이션
- @Service: 서비스 계층의 객체를 위한 어노테이션
- @Repository: DAO와 같은 객체를 위한 어노테이션
- @Component: 일반 객체나 유틸리티 객체를 위한 어노테이션
↳ 스프링이 사용하는 어노테이션의 경우 웹 영역뿐만 아니라 애플리케이션 전체에 사용할 수 있는 객체들을 모두 포함함.
↳ 어노테이션 이용하면 스프링 설정은 '해당 패키지를 조사해서 클래스의 어노테이션들을 이용'하는 설정으로 변경됨.
> context.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"
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 https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="org.zerock.springex.sample"/>
</beans>
↳ 기존 설정과 비교하여 XML 상단에 xmlns(네임 스페이스)가 추가되고 schemaLocation이 변경됨.
↳ component-scan이 내용으로 추가되었는데 속성값으로는 패키지를 지정. 해당 패키지를 스캔해서 스프링의 어노테이션을 인식함.
> 생성자 주입 방식
- 초기 스프링에서는 @Autowired를 멤버 변수에 할당하거나 Setter를 작성하는 방식을 많이 이용했지만, 스프링 3 이후에는 생성자 주입 방식을 더 많이 활용.
- 생성자 주입 방식의 규칙 :
① 주입 받아야 하는 객체의 변수는 final로 작성
② 생성자를 이용해서 해당 변수를 생성자의 파라미터로 지정.
- 생성자 주입 방식은 객체 생성시 문제가 발생하는지 미리 확인 가능하여 필드 주입이나 Setter 주입 방식보다 선호됨.
- Lombok에서는 @RequiredArgsConstructor를 이용해서 필요한 생성자 함수를 자동으로 작성 가능.
@Service
@ToString
@RequiredArgsConstructor
public class SampleService {
private final SampleDAO sampleDAO;
}
▷ 인터페이스를 이용한 느슨한 결합
- 스프링이 의존성 주입을 가능하게 하지만 좀 더 근본적으로 유연한 프로그램을 설계하기 위해 인터페이스를 이용해서 나중에 다른 클래스의 객체로 쉽게 변경할 수 있게 하는것이 좋음.
- 위 SampleService의 SampleDAO를 다른 객체로 변경하려면 서비스가 수정되어야 함. 추상화된 타입을 이용하면 이런 문제를 피할수 있으며 대표적인 것이 인터페이스.
- 인터페이스를 이용하면 실제 객체를 모르고 타입만을 이용해서 코드를 작성하는 것이 가능해짐.
> SampleDAO를 인터페이스로 변경
public interface SampleDAO {
}
- SampleDAO 인터페이스는 실체가 없기 때문에 SampleDAO 인터페이스를 구현한 클래스를 SampleDAOImpl이라는 이름으로 선언
@Repository
public class SampleDAOImpl implements SampleDAO {
}
- SampleDAOImpl에는 @Repository를 이용해서 해당 클래스의 객체를 스프링의 빈으로 처리되도록 구성.
- SampleService는 인터페이스만 바라보고 있기 때문에 실제 객체가 SampleDAOImpl의 인스턴스인지 알 수 없지만, 코드를 작성하는 데에는 아무런 문제가 없음.
↳ 이처럼 객체와 객체의 의존 관계의 실체 객체를 몰라도 가능하게 하는 방식을 '느슨한 결합(Loose coupling)'이라 함.
↳ 느슨한 결합 이용시 나중에 SampleDAO 객체를 다른 객체로 변경하더라도 SampleService 타입을 이용하는 코드를 수정할 일이 없기 때문에 보다 유연한 구조가 됨
> SampleDAO를 다른 객체로 변경한다면?
@Repository
public class EventSampleDAOImpl implements SampleDAO {
}
- 위와 같이 EventSampleDAO로 변경하게 되면, SampleService에 필요한 SampleDAO 타입의 Bean이 두개가 되기 때문에(SampleDAOImpl, EventSampleDAOImpl), 스프링의 입장에서 어떤것을 주입해야 하는지 알 수 없게됨.
↳ 이를 해결하는 가장 간단한 방법은 두 클래스 중 하나를 @Primary라는 어노테이션으로 지정해 주는 것.
↓
@Repository
@Primary
public class EventSampleDAOImpl implements SampleDAO {
}
> @Qualifier
- @Qualifier는 이름을 지정해서 특정한 이름의 객체를 주입받는 방식.
- Lombok과 @Qualifier를 같이 사용하기 위해서는 src/main/java 폴더에 lombok.config 파일 생성 후 아래 내용 작성
lombok.copyableAnnotations += org.springframework.beans.factory.annotation.Qualifier
- SampleDAOImpl에는 'normal', EventSampleDAOImpl에는 'event'라는 이름 지정 후 동작 확인
@Repository
@Qualifier("normal")
public class SampleDAOImpl implements SampleDAO {
}
@Repository
@Qualifier("event")
public class EventSampleDAOImpl implements SampleDAO {
}
- SampleService에서는 특정한 이름의 객체를 사용하도록 함
@Service
@ToString
@RequiredArgsConstructor
public class SampleService {
@Qualifier("normal")
private final SampleDAO sampleDAO;
}
⦁ 스프링의 Bean으로 지정되는 객체들
- 스프링 프레임워크를 이용해서 객체를 생성하고 의존성 주입을 이용할 수 있지만 작성되는 모든 클래스의 객체가 스프링의 Bean으로 처리되는 것은 아님
- 스프링의 Bean으로 등록되는 객체들은 '핵심 배역'을 하는 객체들 → 주로 오랜시간 동안 프로그램 내에 상주하면서 중요한 역할을 하는 역할 중심의 객체들.
- DTO나 VO와 같은 데이터 중심으로 설계된 객체들은 스프링의 Bean으로 등록되지 않음. (이런 객체들은 생명주기가 굉장히 짧고 데이터 보관의 역할만 주로 하기 때문)
⦁ XML이나 어노테이션으로 처리하는 객체
- Bean으로 처리할 때 XML 설정을 이용할 수도 있고, 어노테이션으로 처리할 수 있는데 기준은 '코드를 수정할 수 있는가'로 판단.
- jar 파일로 추가되는 클래스의 객체를 스프링의 Bean으로 처리해야 한다면 해당 코드가 존재하지 않기 때문에 어노테이션을 추가할 수 없다는 문제가 생김. 이러한 객체들은 XML에서 <bean>을 이용하여 처리하고 직접 작성되는 클래스는 어노테이션을 이용하는게 좋음.
▷스프링 준비
- ApplicationContext라는 객체가 스프링에 존재하고 Bean으로 등록된 객체들은 ApplicationContext 내에 생성되어 관리되는 구조.
- ApplicationContext가 웹 애플리케이션에서 동작하려면 웹 애플리케이션이 실행될 때 스프링을 로딩해서 해당 웹 앱 내부에 스프링의 ApplicationContext를 생성하는 작업이 필요함 → 이를 위해서는 web.xml을 이용해서 리스너를 설정
- 스프링 프레임워크의 웹과 관련된 작업은 'spring-webmvc' 라이브러리를 추가해야 설정 가능.
dependencies {
...
implementation group: 'org.springframework', name: 'spring-webmvc', version: '5.3.16'
...
}
- web.xml에 <listener> 설정과 <listener>에 필요한 <context-param> 추가
<?xml ...>
<web-app ...>
...
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/root-context.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
</web-app>
> DataSource 구성
- 톰캣과 스프링이 연동되는 구조를 완성했다면 웹 애플리케이션에서 필수인 데이터베이스 관련 설정을 추가.
- dependencies에 db관련 라이브러리 추가.
- xml 파일에 config와 dataSource bean 추가.
> 테스트
@Log4j2
@ExtendsWith(SpringExtension.class)
@ContextConfiguration(locations="file:src/main/webapp/WEB-INF/root-context.xml")
public class SampleTests {
@Autowired
private SampleService service;
@Autowired
private DataSource dataSource;
@Test
public void testService() {
log.info(service);
Assertions.assertNotNull(service);
}
@Test
public void testConnection() throws Exception {
Connection conn = dataSource.getConnection();
log.info(conn);
Assertions.assertNotNull(conn);
conn.close();
}
}
- SampleTests에는 root-context.xml에 선언된 HikariCP를 주입받기 위해 DataSource 타입 변수를 선언하고 @Autowired로 주입 받도록 함.
▶ MyBatis와 스프링 연동
- 스프링 프레임워크의 중요한 특징으로 다른 프레임워크들을 쉽게 결합해서 사용할 수 있다는 점이 있음. (스프링 프레임워크가 웹이나 DB와 같이 특정한 영역을 구애받지 않고 시스템의 객체지향 구조를 만드는데 이용된다는 성격 때문)
- DB와 관련해서 스프링은 자체적으로 'spring-jdbc'와 같은 라이브러리를 써서 구현할 수도 있고, MyBatis나 JPA 프레임워크 이용 방식도 있음.
- MyBatis는 'Sql Mapping Framework'라고 표현됨. (SQL 실행 결과를 객체지향으로 매핑)
> MyBatis 사용의 장점
- 기존의 SQL을 그대로 사용 가능
- PreparedStatement/ResultSet의 처리 → 기존에 프로그램을 작성해서 하나씩 처리해야 하는 파라미터나 ResultSet의 getXXX()를 MyBatis가 알아서 처리해 주기 때문에 기존에 비해서 많은 양의 코드를 줄일 수 있음.
- Connection/PreparedStatement/ResultSet의 close() 처리 → MyBatis와 스프링을 연동해서 사용하는 방식을 이용하면 자동으로 close() 처리 가능
- SQL의 분리 → 별도의 파일이나 어노테이션 등을 이용해서 SQL을 선언함. 파일을 이용하는 경우 SQL을 별도의 파일로 분리해서 운영 가능.
▷ MyBatis와 스프링의 연동 방식
- 스프링은 MyBatis와 연동을 쉽게 처리할 수 있는 라이브러리와 API들을 제공.
- MyBatis를 단독으로 개발하고 스프링에서 DAO를 작성해서 처리하는 방식 : 기존의 DAO에서 SQL의 처리를 MyBatis를 이용하는 구조로써 완전히 MyBatis와 스프링 프레임워크를 독립적인 존재로 바라보고 개발하는 방식
- MyBatis와 스프링을 연동하고 Mapper 인터페이스만 이용하는 방식 : 스프링과 MyBatis 사이에 'mybatis-spring'이라는 라이브러리를 이용해서 스프링이 DB 전체에 대한 처리를 하고 MyBatis는 일부 기능 개발에 활용하는 방식. 개발시에는 Mapper 인터페이스라는 방식을 이용해서 인터페이스만으로 모든 개발이 가능한 방식.
> MyBatis를 위한 라이브러리
- 스프링 관련 : spring-jdbc, spring-tx
- MyBatis 관련 : mybatis, mybatis-spring
> MyBatis를 위한 스프링의 설정 => SqlSessionFactory
- MyBatis를 이용하기 위해서는 스프링에 설정해둔 HikariDataSource를 이용해서 SqlSessionFactory라는 Bean 설정.
- root-context.xml에 'mybatis-spring' 라이브러리에 있는 클래스를 이용해서 <bean>을 등록
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
</bean>
> Mapper 인터페이스 활용
import org.apache.ibatis.annotations.Select;
public interface TimeMapper {
@Select("select now()")
String getTime();
}
- TimeMapper는 DB의 현재 시각을 문자열로 처리하도록 구성.
- MyBatis에는 @Select 어노테이션을 이용해서 쿼리를 작성할 수 있는데 JDBC와 마찬가지로 ';'를 사용하지 않으므로 주의.
- 작성된 Mapper 인터페이스를 root-context.xml에 등록해줘야함.
<mybatis:scan base-package="인터페이스 있는 패키지.."></mybatis:scan>
- <mybatis:scan> 태그를 이용해서 설정 추가. 또한 xml 파일 상단의 xmls, xsi 설정에 mybatis-spring 관련 설정을 추가해야 함.
<beans xmlns:mybatis="http://mybatis.org/schema/mybatis-spring"
xsi:schemaLocation="http://mybatis.org/schema/mybatis-spring>
> 테스트
@Log4j2
@ExtendWith(SpringExtension.class)
@ContextConfiguration(location="file:src/main/webapp/WEB-INF/root-context.xml")
public class TimeMapperTests {
@Autowired(required = false)
private TimeMapper timeMapper;
@Test
public void testGetTime() {
log.info(timeMapper.getTime());
}
}
- @Autowired(required = false) 속성 지정시 해당 객체를 주입 받지 못하더라도 예외가 발생하지 않음
- MyBatis와 스프링을 연동하고 매퍼 인터페이스를 활용하는 방식은 개발자가 실제 동작하는 클래스와 객체를 생성하지 않고 스프링에서 자동으로 생성되는 방식을 사용.
↳ 개발자가 직접 코드를 수정할 수 없다는 단점이 있지만 인터페이스만으로도 개발을 완료할 수 있다는 장점이 있음
'Spring' 카테고리의 다른 글
서블릿 API 필수적 개념 (세션, 쿠키, 필터, 리스너) (0) | 2023.09.17 |
---|---|
Spring Tiles (스프링 타일즈) (0) | 2023.07.25 |
웹 페이지 파일(이미지) 업로드 & 페이지네이션 (0) | 2023.07.24 |
(Spring 관련 정보) Dispatcher Servlet, Filter, Interceptor에 대하여 (0) | 2023.07.18 |
스프링 CRUD Library 프로그램 버전별 정리 後 (0) | 2023.07.17 |
댓글