본문 바로가기
Spring

스프링 & 스프링 Web MVC 주요 개념

by SuldenLion 2024. 6. 23.
반응형

▶ 의존성 주입과 스프링

- 스프링 프레임워크는 웹이라는 제한적인 용도로만 쓰이는게 아닌 객체지향의 "의존성 주입(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'와 같은 라이브러리를 써서 구현할 수도 있고, MyBatisJPA 프레임워크 이용 방식도 있음.

- 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와 스프링을 연동하고 매퍼 인터페이스를 활용하는 방식은 개발자가 실제 동작하는 클래스와 객체를 생성하지 않고 스프링에서 자동으로 생성되는 방식을 사용.

개발자가 직접 코드를 수정할 수 없다는 단점이 있지만 인터페이스만으로도 개발을 완료할 수 있다는 장점이 있음

 

 

 

 

 

 

반응형

댓글