1. 스프링의 시작
스프링 프레임워크는 웹이라는 제한적 용도로만 쓰이는 것이 아니라 객체지향의 의존성 주입 dependency injection 기법을 적용할 수 있는 객체지향 프레임워크
- 스프링 프레임워크는 로드 존슨이 2002년도에 집필했던 'J2EE 설계 및 개발 wrox'이라는 책의 예제 코드에서 시작되었는데 말 그대로 효과적이고 가볍게 J2EE를 이용할 수 있다는 것을 증명하면서 예제의 코드를 발전시킨 것
- 2000년 당시 자바 진영에서 JavaEE의 여러가지 스펙을 정의하고 비대해지는 동안 스프링 프레임워크는 반대로 '경량 light weight 프레임워크'를 목표로 만들어짐
- 스프링이 등장할 때 여러 종류의 프레임워크들이 비슷한 사상(경량화, 단순화)으로 등장했지만 다른 프레임워크들과 달리 스프링 프레임워크는 개발과 설계 전반에 관련된 문제들을 같이 다루었기 때문에 결론적으로 가장 성공한 프레임워크로 기록
- 스프링 프레임워크는 가장 중요한 '코어core' 역할을 하는 라이브러리와 여러 개의 추가적인 라이브러리를 결합하는 형태로 프로젝트를 구성. 가장 대표적으로 웹 MVC구현을 쉽게 할 수 있는 'Spring Web MVC'나 JDBC 처리를 쉽게 할 수 있는 'MyBatis'를 연동하는 'mybatis-spring'과 같은 라이브러리가 그러한 예.
2. 의존성 주입
스프링이 객체지향 구조를 설계할 때 받아들인 개념은 '의존성 주입 dependency injection'이라는 사상.
✓ 의존성 주입은 어떻게 하면 '객체와 객체 간의 관계를 더 유연하게 유지할 것인가?'에 대한 고민에서 나옴
✓ 즉 의존성이란 하나의 객체가 자신이 해야 하는 일을 하기 위해서 다른 객체의 도움이 필수적인 관계를 의미
✓ 스프링 프레임워크는 바로 이런 점을 프레임워크 자체에서 지원. 다양한 방식으로 필요한 객체를 찾아서 사용할 수 있도록 XML 설정이나 자바 설정 등을 이용
3. ApplicationContext 와 빈 Bean
👻 서블릿이 존재하는 공간을 서블릿 콘텍스트 ServletContext라고 했던 것 처럼, 스프링에서는 빈 Bean 이라고 부르는 객체들을 관리하기 위해서 ApplicationContext 라는 존재를 활용
코드의 경우 ApplicationContext는 root-context.xml을 이용해서 스프링이 실행 ▼ ApplicationContext 객체가 생성 ▼ root-context.xml을 읽으면 SampleService와 SampleDAO가 해당 클래스의 객체를 생성해서 관리 ▼ 테스트를 실행하면 @Autowired가 처리된 부분에 맞는 타입의 빈 Bean 이 존재하는지를 확인 ▼ 이를 테스트 코드 실행 시에 주입 ▼ 멤버변수에 직접 @Autowired를 선언하는 방식을 '필드 주입 Field Injection' 방식 |
4. SampleDAO 주입하기
@Autowired를 이용하면 필요한 타입을 주입받을 수 있다는 사실을 이용해서 SampleService를 다음과 같이 변경
📌 Lombok의 @ToString을 적용한 부분과 SampleDAO의 변수로 선언하고 @Autowired를 적용
📌 테스트 코드를 실행하면 SampleService 객체 안에 SampleDAO 객체가 주입된 것을 확인
👾 스프링을 이용할 때는 클래스를 작성하거나 객체를 직접 생성하지 않음
👾 이 역할은 스프링 내부에서 이루어지며 ApplicationContext가 생성된 객체들을 관리
- 서블릿의 url 매핑의 경우 web.xml에 <servlet>이라는 태그를 이용해서 서블릿 클래스의 이름과 경로를 전부 기록해야 했지만, 최근에는 @WebServlet 어노테이션이 이를 대신하는 것처럼, 스프링도 비슷한 방식으로 발전, 초기 스프링 버전에서는 XML 파일에 <bean>이라는 것을 이용해서 설정하는 방식이 2.5버전 이후에 어노테이션 형태로 변화되면서 예전에 비해 편리하게 설정이 가능
5. @Repository, @Service
👩🏻💻 서블릿에서도 @WebServlet이나 @WebFilter와 같이 다양한 어노테이션이 존재하듯이 스프링 프레임워크는 애플리케이션 전체를 커버하기 때문에 다양한 종류의 어노테이션을 사용하도록 작성
@Controller : MVC의 컨트롤러를 위한 어노테이션
@Service : 서비스 계층의 객체를 위한 어노테이션
@Repository : DAO와 같은 객체를 위한 어노테이션
@Component : 일반 객체나 유틸리티 객체를 위한 어노테이션
💡 DAO(Data Access Object) "실제로 DB의 데이터에 접근하는 객체"
- DAO는 Service와 DB를 연결하는 역할을 하며, 실제로 DB에 접근하여 data를 삽입, 삭제, 조회, 수정 등 CRUD 기능을 수행
💡 DTO(Data Transfer Object) "계층 간 데이터 교환을 위한 객체"
- 로직을 가지지 않고 getter/setter 메소드만 가진 순수한 데이터 객체 클래스(Java Beans)로 DB에서 데이터를 얻어 Service나 Controller 등으로 보낼 때 사용
💡 VO(Value Object) "변경 불가능하며 오직 읽기만 가능 (Read-Only)"
- DTO는 setter를 가지고 있어 값을 변경할 수 있지만, VO는 getter만을 가지기 때문에 읽기만 가능하고 수정은 불가능
출처: https://hstory0208.tistory.com/entry/Spring-DAO-DTO-VO란-각각의-개념에-대해-알아보자
👩🏻💻 스프링이 사용하는 어노테이션의 경우 웹 영역뿐만 아니라 애플리케이션 전체에 사용할 수 있는 객체들을 포함
👩🏻💻 어노테이션을 이용하게 되면 스프링 설정은 <bean>이 아니라 '해당 패키지를 조사해서 어노테이션들을 이용'하는 설정으로 변경
📌 기존 설정과 비교해 보면 XML 위쪽의 xmlns 네임 스페이스가 추가되고 schemaLocation이 변경
📌 내용에는 component-scan이 추가되었는데 속성값으로는 패키지를 지정
📌 'component-scan'은 해당 패키지를 스캔해서 스프링의 어노테이션들을 인식
🎃 SampleDAO는 해당 클래스의 객체가 스프링에서 빈 Bean으로 관리될 수 있도록 @Repository 라는 어노테이션을 추가
🎃 SampleService에는 @Service 어노테이션을 추가
생성자 주입 방식
👾 초기 스프링에서는 @Autowired를 멤버 변수에 할당하거나, Setter를 작성하는 방식을 많이 이용해 왔지만, 스프링 3 이후에는 생성자 주입이라고 부르는 방식을 더 많이 활용.
👾 생성자 주입 방식은 객체를 생성할 때 문제가 발생하는 지를 미리 확인할 수 있기 때문에 필드 주입이나 Setter 주입 방식 보다 선호되는 방식
👾 Lombok에서는 생성자 주입을 보다 간단하게 작성할 수 있는데 @RequiredArgsConstructor를 이용해서 필요한 생성자 함수를 자동으로 작성할 수 있음
💫 생성자 주입 방식은 다음과 같은 규칙으로 작성
- 주입 받아야 하는 객체의 변수는 final로 작성
- 생성자를 이용해서 해당 변수를 생성자의 파라미터로 지정
6. 인터페이스를 이용한 느슨한 결합
스프링이 의존성 주입을 가능하게 하지만 '좀 더 근본적으로 유연한 프로그램을 설계하기 위해서는 인터페이스를 이용'해서 나중에 다른 클래스의 객체로 쉽게 변경할 수 있도록 하는 것이 좋음.
⚡️ 예를 들어 코드에서 SampleDAO를 다른 객체로 변경하려면 결론적으로 SampleService 코드 역시 수정해야 함. 추상화된 타입을 이용하면 이러한 문제를 피할 수 있는데 가장 대표적인 것이 인터페이스. 인터페이스를 이용하면 실제 객체를 모르고 타입만을 이용해서 코드를 작성하는 일이 가능해 짐.
1) SampleDAO를 인터페이스로 변경하기
🎃 클래스로 작성된 SampleDAO을 인터페이스로 수정. @Repository 삭제
🎃 SampleService는 SampleDAO라는 인터페이스를 보게 되었지만 코드상의 변경은 필요하지 않음
🎃 SampleDAO 인터페이스는 구현 코드가 없기 때문에 구현한 클래스를 SampleDAOImpl 이라는 이름으로 선언
🎃 SampleDAOImpl에는 @Repository를 이용해서 해당 클래스의 객체를 스프링의 빈 Bean으로 처리되도록 구성
SampleService의 입장에서는 인터페이스만 바라보고 있기 때문에 실제 객체가 SampleDAOImpl의 인스턴스인지 알수 없지만, 코드를 작성하는 데에는 아무런 문제가 없음. 이처럼 객체와 객체의 의존 관계의 실제 객체를 몰라도 가능하게 하는 방식을 '느슨한 결합 Loose coupling'이라고 함. 느슨한 결합을 이용하면 나중에 SampleDAO 타입의 객체를 다른 객체로 변경하더라도 SampleService 타입을 이용하는 코드를 수정할 일이 없기 때문에 보다 유연한 구조가 됨.
2) 다른 SampleDAO 객체로 변경해 보기
🎃 예를 들어 특정한 기간에만 SampleDAO를 다른 객체로 변경해야 되는 경우 EventSampleDAOImpl이라는 클래스를 작성
이렇게 되면 SampleService에 필요한 SampleDAO 타입의 빈 Bean이 두 개 SampleDAOImpl, EventSampleDAOImpl 가 되기 때문에 스프링 입장에서는 어떤 것을 주입해야 하는지 알 수 없음. 테스트 코드를 실행하면 어떤 클래스의 객체를 사용해야 하는지 알 수 없으므로 다음과 같은 에러가 발생
Error creating bean with name 'sampleService' defined in file [E:\work\dev\projects\projects-spring intellij\springex\build\classes\java\main\com\nomadlab\springex\sample\SampleService.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.nomadlab.springex.sample.SampleDAO' available: expected single matching bean but found 2: eventSampleDAOImpl,sampleDAOImpl
스프링이 기대하는 것은 SampleDAO 타입의 객체가 하나 single 이길 기대했지만 2개가 발견.
이를 해결하는 가장 간단한 방법은 두 클래스 중에 하나를 @Primary라는 어노테이션으로 지정해 주는 것
3) @Qualifier 이용하기
👾 @Primary를 이용하는 방식 외에도 @Qualifier를 이용하는 방식도 있음
👾 @Qualifier는 이름을 지정해서 특정한 이름의 객체를 주입받는 방식
📌 Lombok과 @Qualifier를 같이 이용하기 위해서는 src/main/java 폴더에 lombok.config 파일을 생성
🎃 동작을 확인하기 위해서 SampleDAOImpl과 EventSampleDAOImpl 에는 @Qualifier를 적용.
🎃 SampleDAOImpl 에는 normal 이라는 이름을 지정
🎃 EventSampleDAOImpl에는 event 라는 이름을 지정
🎃 SampleService에는 특정한 이름의 객체를 사용하도록 수정
🎃 normal 이름을 가진 SampleDAOImpl이 주입되는 것을 확인
4) 스프링의 빈 Bean 으로 지정되는 객체들
👾 스프링 프레임워크를 이용해서 객체를 생성하고 의존성 주입을 이용할 수 있다는 사실을 알았지만 작성되는 모든 클래스의 객체가 스프링의 빈 Bean으로 처리되는 것은 아님
👾 스프링의 빈 Bean으로 등록되는 객체들은 쉽게 말해서 '핵심 배역'을 하는 객체들로 오랜 시간 동안 프로그램 내에 상주하면서 중요한 역할을 하는 '역할' 중심의 객체들
👾 반대로 DTO나 VO와 같이 '역할' 보다는 '데이터'에 중심을 두고 설계된 객체들은 스프링의 빈 Bean을 등록하지 않음
특히 DTO의 경우 생명주기가 굉장히 짧고, 데이터 보관이 주된 역할이기 때문에 스프링에서 빈 Bean으로 처리하지 않음
5) XML이나 어노테이션으로 처리하는 객체
👾 빈 Bean 으로 처리할 때 XML 설정을 이용할 수도 있고, 어노테이션을 처리할 수도 있지만, 이에 대한 기준은 '코드를 수정할 수 있는가'로 판단
👾 예를 들어 jar 파일로 추가되는 클래스의 객체를 스프링의 빈Bean으로 처리해야 한다면 해당 코드가 존재하지 않기 때문에 어노테이션을 추가할 수가 없다는 문제가 생김. 이러한 객체들은 XML에서 <bean>을 이용해서 처리하고, 직접 처리되는 클래스는 어노테이션을 이용하는 것이 좋음.
7. 웹 프로젝트를 위한 스프링 준비
💡 스프링의 구조를 보면 ApplicatonContext라는 객체가 존재하고 빈으로 등록된 객체들은 ApplicatonContext 내에 생성되어 관리되는 구조.
⚡️ 이렇게 만들어진 ApplicatonContext가 웹 애플리케이션에 동작하려면 웹 애플리케이션이 실행될 때 스프링을 로딩해서 해당 웹 애플리케이션 내부에 스프링의 ApplicatonContext를 생성하는 작업이 필요하게 되는데 이를 위해서 web.xml을 이용해서 리스너를 설정
⚡️ 스프링 프레임워크의 웹과 관련된 작업은 'spring-webmvc'라이브러리를 추가해야만 설정이 가능
▶️ build.gradle파일에 spring-mvc 라이브러리를 추가
▶️ WEB-INF 폴더 아래 web.xml에 <listener>설정과 <listener>에 필요한 <context-param>을 추가
💫 설정이 추가된 후에 톰캣을 실행하면 스프링과 관련된 로그가 기록되면서 실행
8. DataSource 구성하기
톰캣과 스프링이 연동되는 구조를 완성했다면 데이터베이스 관련 설정을 추가
📌 build.gradle에 MySQL과 HikariCP 관련 라이브러리를 추가
// https://mvnrepository.com/artifact/mysql/mysql-connector-java
implementation 'mysql:mysql-connector-java:8.0.33'
// https://mvnrepository.com/artifact/com.zaxxer/HikariCP
implementation 'com.zaxxer:HikariCP:5.0.1'
📌 root-context.xml에 HikariCP 설정하기
<context:component-scan base-package="com.example.sping_class.sample" />
<bean id="hikariConfig" class="com.zaxxer.hikari.HikariConfig">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/sample" />
<property name="username" value="root"/>
<property name="password" value="비밀번호"/>
<property name="dataSourceProperties">
<props>
<prop key="cachePrepStmts">true</prop>
<prop key="prepStmtCacheSize">250</prop>
<prop key="prepStmtCacheSqlLimit">2048</prop>
</props>
</property>
</bean>
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close">
<constructor-arg ref="hikariConfig" />
</bean>
- HikariCP를 사용하기 위해서는 HikariConfig 객체와 HikariDataSource를 초기화해야함
- root-context.xml을 이용해서 HikariConfig 객체와 HikariDataSource 객체를 설정
- hikariConfig에는 id 속성이 적용되어 있고 HikariDataSource는 <constructor-arg ref="hikariConfig" />로 id 값을 참조해서 사용
- HikariDataSource는 javax.sql의 DataSource 인터페이스의 구현체이므로 테스트 코드를 통해서 설정에 문제가 없는지 확인
🎃 root-context.xml에 선언된 HikariCP를 주입받기 위해서 DataSource타입의 변수를 선언하고 @Autowired를 이용해서 주입 되도록 구성
👩🏻💻 스프링은 필요한 객체를 스프링에서 주입해 주기 때문에 개별적으로 클래스를 작성해서 빈으로 등록해 두기만 하면 원하는 곳에서 쉽게 다른 객체를 사용할 수 있음. 이런 특징으로 인해 스프링 프레임워크는 웹이나 데이터베이스 같은 특정한 영역이 아닌 전체 애플리케이션의 구조를 설계할 때 사용
[ 내용 참고 : IT 학원 강의 ]
'Spring & Spring Boot' 카테고리의 다른 글
[Spring] Todo 목록 | 조회 기능 개발 (0) | 2024.04.28 |
---|---|
[Spring] Todo 기능 개발 | 한글 처리 필터 설정 | 유효성 검사 @Valid (0) | 2024.04.28 |
[Spring] 프로젝트 구현 준비 | 부트스트랩 적용 | DB 처리 (0) | 2024.04.27 |
[Spring] 스프링 Web MVC (0) | 2024.04.24 |
[Spring] MyBatis와 스프링 연동 (0) | 2024.04.20 |