Spring 2024.06.12

HI_DO·2024년 6월 12일
post-thumbnail

ApplicationContext

  • 서블릿이 존재하는 공간을 서블릿 컨텍스트라고 했다. 스프링에서는 빈(Bean)이라고 부르는 객체를 관리하기 위해서 ApplicationContext라는 존재를 활용해야 한다.
  • ApplicationContext는 root-context.xml을 이용해서 스프링이 실행되고 ApplicationContext 객체가 생성된다.
  • root-context.xml을 읽으면 SampleService와 SampleDAO가 [bean]으로 지정 되어있어 해당 클래스의 객체를 생성해서 관리하기 시작한다.

Context:component-scan

  • 스프링을 이용할때는 클래스를 작성하거나 객체를 직접 생성하지 않는다. 이 역할은 스프링 내부에서 이루어지며 ApplicationContext가 생성된 객체들을 관리한다.
  • 개발자가 직접 객체를 생성하지 않는 방식이 서블릿과도 유사하다. 서블릿을 생성하면 톰캣이 웹 애플리케이션을 실행하고 필요할 때 서블릿 객체를 만드는 것과 비슷한 방식이다.

@Service, @Repository

  • 서블릿에서도 @WebServlet이나 @WebFilter와 같은 다양한 에너테이션이 존재하듯이 스피링 프레임워크는 애플리케이션 전체를 커버하기 때문에 다양한 종류의 네너테이션을 사용하도록 작성되어 있다.
  • @Controller: MVC의 컨트롤러를 위한 에너테이션
    @Serive: 서비스 계층의 객체를 위한 에너테이션
    @Repository: DAO와 같은 객체를 위한 에너테이션
    @Component: 일반 객체나 유틸리티객체를 위한 에너테이션
  • 에너테이션 이름으로 스프링이 사용하는 웹 영역뿐만 아니라 애플리케이션 전체에 사용할수 있는 객체들을 망라하고 있다.

생성자 주입 방식

  • 초기 스프링에서는 @Autowired를 멤버 변수로 할당하거나 Setter를 작성하는 방식으로 이용했지만, 스피링 3 이후에는 생성자 주입 방식이라고 부르는 방식을 더 많이 활용하고 있다.
  • 생성자 주입 방식 규칙
    1) 주입 받아야하는 객체의 변수는 final로 작성되어야 한다.
    2) 생성자를 이용해서 해당 변수를 생성자의 파라미터로 지정한다.
  • Lombok에는 생성자 주입을ㅇ 간단히 작성할 수 있는데 @RequiredArsContructor를 이용해서 필요한 생성자를 자동으로 작성할 수 있다.

인터페이스를 이용한 느슨한 결합

  • 스프링이 의존성 주입을 가능하게 하지만 좀 더 근본적으로 유연한 프로그램을 설계하기 위해서는 인터페이스를 이용해서 나중에 다른 클래스의 객체로 쉽게 변경할 수 있도록 하는 것이 좋다.
  • 예를 들어 SampleDAO를 다른 객체로 변경하려면 결론적으로 SampleSerice 코드 역시 수정되어야 한다. 추상화 된 타입을 이용하면 이러한 문제를 피할 수 있는데 가장 대표적인 것이 인터페이스이다. 인터페이스를 이용하면 실제 객체를 모르고 타입만을 이용해서 코드를 작성하는 일이 가능하다.
  • 객체와 객체의 의존 관계의 실제 객체를 몰라도 가능하게 하는 방식을 느슨한 결합이라고 한다.
  • 느슨한 결합을 이용하면 나중에 SampleDAO 타입의 객체를 다른 객체로 변경하더라도 SampleService 타입을 이용하는 코드는 수정할 일이 없기때문에 보다 유연한 구조가 된다.

@Qualifier

  • 이름을 지정해서 특정한 이름의 객체를 주입받는 방식.
  • Lombok과 @Qualifier를 같이 사용하기 위한 방법.
  • main > java > lombok.config 파일 생성
  • lombok.copyableAnnotations += org.springframework.beans.factory.annotation.Qualifier

스프링의 빈(Bean)으로 지정되는 객체들

  • 객체들은 쉽게 말해서 ‘핵심 역할’을 하는 객체들이다
  • 스프링의 빈으로 등록되는 객체들은 오랜 시간 동안 프로그램 내에서 상주하면서 중요한 역할을 하는 객체들이다
  • 반대로 말하면, 역할이 아닌 데이터를 중심으로 두고 설계되는 객체들(DTO,VO)은 Bean으로 등록하지 않는다. 특히 DTO의 경우에는 생명주기가 짧고, 데이터 보관이 주된 역할이기 때문에 빈으로 처리하지 않는다
  • ApplicationContext가 웹 애플리케이션에서 동작하려면 웹 애플리케이션이 실행될때 스프링을 로딩해서 해당 웹 애플리케이션 내부에 스프링의 ApplicationContext를 생성하는 작업이 필요하게 되는데 이를 위해서는 web.xml을 이용해서 리스너를 설정한다.
  • 톰캣과 스프링을 연동하는 구조를 완성
  • 데이터베이스 관련 설정을 추가해야 한다.
public enum ConnectionUtil {
    INSTANCE;
    private HikariDataSource ds;
    ConnectionUtil() {
        HikariConfig config = new HikariConfig();       config.setDriverClassName("org.mariadb.jdbc.Driver");        config.setJdbcUrl("jdbc:mariadb://localhost:3307/webdb");
        config.setUsername("webuser");
        config.setPassword("webuser");  config.addDataSourceProperty("cachePrepStmts", "true");      config.addDataSourceProperty("prepStmtCacheSize", "250");      config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
        ds = new HikariDataSource(config);
    }
        public Connection getConnection()throws Exception{
            return ds.getConnection();
        }
}

-> 스프링을 이용한다면 이 설정을 스프링의 빈으로 처리되어야 한다

  • root-context.xml
    -> 빈으로 설정이 되어야 하는 패키지 처리.
    데이터베이스의 설정 연결
  • 스프링은 필요한 객체를 스프링에서 주입해 주기 때문에 개별적으로 클래스를 작성해서 빈(bean)으로 등록해 두기만 하면 원하느 곳에서 쉽게 다른 객체를 사용할 수 있다.
  • 이런 특징으로 인해 스프링은 웹이나 데이터베이스와 같은 특정한 영역이 아닌 전체 애플리케이션의 구조를 설계할 때 사용된다.

JDBC -> MyBatis를 이용해서 프로젝트에 적용

  • PreparedStatement, ResultSet, Connection의 처리를 안해도 된다,
  • SQL을 분리하여 선언할 수 있다.
  • MyBatis를 위한 라이브러리
    -> 스프링관련: spring-jbdc, spring-tx
    MyBatis: mybatis, mybatix-spring
  • MyBatis를 위한 스프링의 설정(SqlSessionFactory)_root-context.xml에 MyBatis와 스프링을 연동하고 Mapper 인터페이스를 이용하는 방식
  • 스프링과 MyBatis 사이에 'mybatis-spring'이라는 라이브러리를 이용해서 스프링이 데이터베이스 전체에 대한 처리를 하고 MyBatis는 일부 기능 개발에 활용하는 방식. 개발 시에는 Mapper 인터페이스 방식을 이용해서 인터페이스만으로 모든 개발이 가능한 방식

@AutoWired(required = false)

  • 해당 객체를 주입받지 못하더라도 예외가 발생하지 않는데, 인텔리제이, 이클립스의 경우 @Service, @Repository와 같이 직접 스프링의 빈(Bean)으로 등록된 경우가 아니면 경고가 발생하므로 이를 방지하기 위해 사용된다.

xml로 SQL 분리하기

  • MyBatis를 이용할때 sql은 @Select와 같은 에너테이션을 이용해서 사용하기도 하지만 대부분은 sql을 별도의 파일로 분리하는 것을 권장한다.
    -xml을 사용하는 이유는 sql이 길어지면 이를 에너테이션으로 처리하기가 복잡하기 때문이기도 하고 에너테이션이 나중에 변경되면 프로젝트 전체를 다시 빌드하는 작업이 필요하기 때문에 단순 파일로 사용하는 것이 편리하다.
    [property name="mapperLocations" value="classpath:/mappers/*/.xml"][/property]
    xml mapper 파일의 위치
  • classpath 접두어를 이용해서 인식되는 경로이고 mappers 폴더 밑에 폴더가 있어도 관계없도록 '*'와 '.xml'을 지정

순서

    implementation 'org.mybatis:mybatis:3.5.15'
    implementation 'org.mybatis:mybatis-spring:3.0.3'
    implementation 'org.springframework:spring-jdbc:6.1.2'
    implementation 'org.springframework:spring-tx:6.1.2'
  1. root-context.xml에
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
    </bean>
  1. 애너테이션으로 설정
package springex.mapper;
import org.apache.ibatis.annotations.Select;
public interface TimeMapper {
   @Select("select now()")
   String getTime();
}
  1. root-context.xml에 <mybatis:scan>을 인식할수 있도록 등록
xmlns:mybatis="http://mybatis.org/schema/mybatis-spring"
http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring.xsd">
 <mybatis:scan base-package="com.springex.mapper"></mybatis:scan>
  1. 인식하는지 tests 작성
@Log4j2
@ExtendWith(SpringExtension.class)
@ContextConfiguration(locations="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());
   }
}
  1. 애너테이션으로 하는 방법말고 xml로 sql분리하는 방법을 사용.
springex > mapper 패키지생성 > TimeMapper2.interface
package springex.mapper;
public interface TimeMapper2 {
   String getNow();
}
  1. main > resources > mappers 디렉터리 추가후 TimeMapper2.xml 생성
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.springex.mapper.TimeMapper2">
    <select id="getNow" resultType="string">
        select now()
    </select>
</mapper>
  1. root-context.xml에 MyBatis 설정에 xml 파일을 인식하도록 설정 추가
<property name="mapperLocations" value="classpath:/mappers/**/*.xml"></property>

스프링 웹 MVC

  • DispatcherServlet과 Front Controller
    -> 요청(Request)이 오면 반드시 DispatcherServlet라는 존재를 통해서 실행이 된다.
  • 결론은 스프링 MVC에서는 DispatcherServlet이라는 객체가 프론트 컨트롤러의 역할을 수행한다

root-context.xml

  • 애플리케이션의 핵심적인 빈
  • 데이터베이스 연결, 서비스 계층 구성 등의 애플리케이션의 전반에 걸친 설정 정의

servlet-context.xml

  • 스프링 mvc의 DispatcherServlet에 의한 처리되는 설정을 담당

web.xml

  • 웹 애플리케이션의 배치 설명자(Deployment Descriptor)로서, 서블릿 컨테이너에 애플리케이션을 배포할때 필요한 구성 요소를 정의
  • Servlet, Filter, Listener와 같은 웹 구성 요소의 매핑 및 설정
  • ApplicationContext 구성하기 위해[Listener] 초기화 설정
  • [servlet] 설정은 DispatcherServlet을 등록하는데 DispatcherServlet이 로딩할 때 servlet-context.xml을 이용하도록 설정한다.

@RequestMapping

  • 스프링 컨트롤러에서 가장 많이 사용하는 애너테이션
  • 특정 경로의 요청을 지정하기 위해서 사용
  • 참고로 servlet 중심의 MVC는 Servlt의 상속받아서 doGET(), doPost()와 같은 제한적인 메서드를 오버라이드해서 사용한다
  • 스프링 MVC의 경우 하나의 컨트롤러를 이용해서 여러 경로의 호출 모두 처리할 수 있다.

파라미터 자동 수집과 변환

  • 파라미터 자동 수집은 DTO나 VO등을 메서드의 파라미터로 설정하면 자동으로 전달되는 HttpServletRequest의 파라미터들을 수집해 주는 기능이다.
  • (Failed to convert value of type 'java.lang.String'to required type 'java.time.LocalDate') -> 특정한 타입을 처리하는 Formatter라는 것을 이용해야 한다. 문자열을 포맷해서 특정한 객체로 변환하는 경우에 사용할 수 있다.
  • <mvc:annotation-friven conversion-service="conversionService"/> conversionServie라는 빈(Bean)을 등록한 후에 스프링MVC를 처리할 때 이용하겠다.

객체 자료형의 파라미터 수집

  • 기본 자료형과 달리 객체 자료형을 파라미터로 처리하기 위해서는 객체가 생성되고 setXXX()을 이용해서 처리한다
  • Lombok을 활용하여 @Setter, @Date를 이용하여 처리할 수 있다.

Model

  • 서블릿 request.setAttibute()를 이용해서 데이터 담아서 jsp까지 전달
  • 스프링 MVC에서는 Model이라는 객체를 이용해서 처리한다
  • Java Beans와 @ModelAttribute
    스프링 MVC 컨트롤러는 특이하게도 파라미터로 getter/setter를 이용하는 Java Beans의 형식의 사용자 정의 클래스가 파라미터 인 경우에는 자동으로 화면까지 객체를 전달한다.
    파라미터로 TodoDTO를 받는 경우에

RedirectAttributes

  • Post 방식으로 처리를 하고 Redirect를 해서 get방식으로 특정한 페이지로 이동하는 PRG패턴
  • 스프링 MVC에서는 RedirectAttributes 로 처리한다
    1) addAttribute(키,값): 리다이렉트할 때 쿼리 스트링이 되는 값을 지정
    2) addFlashAttribute(키,값): 일회용으로만 데이터를 전달하고 삭제되는 값을 지정
  • addAttribute()로 데이터를 추가하면 리다이렉트할 URL에 쿼리 스트링 추가되고, addFlashAttribute()를 이용하면 URL에 보이지는 않지만, JSP에서는 일회용으로 사용할 수 있다.

스프링 MVC의 예외 처리

  • 컨트롤러에서 발생하는 예외를 처리할 때 가장 일반적인 방식은 @ControllerAdvice를 사용하는 방식이다.
  • @ControllerAdvice가 선언된 클래스는 스프링의 Bean으로 처리된다
  • @ExceptionHandler: 어떤 exception인지 인지하는 에너테이션
  • @ResponseBody: 문자열,JSON타입을 그대로 브라우저에 전송하는 방식

404상태에 맞는 화면을 별도로 맘ㄴ들 경우

  • CommonExceptionAdvice에 추가
@ExceptionHandler(NoHandlerFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public String notFound(){
        return "custom404";
    }
    ```
   
- views > custom404.jsp 파일 생성
WEB-INF > web.xml 수정

페이지를 찾을 수 없습니다!

appServlet org.springframework.web.servlet.DispatcherServlet contextConfigLocation /WEB-INF/servlet-context.xml
    <init-param>
        <param-name>throwExceptionIfNoHandlerFound</param-name>
        <param-value>true</param-value>
    </init-param>


    <load-on-startup>1</load-on-startup>
</servlet>

서버 다시 돌리고 주소 잘못치면 "페이지를 찾을수 없습니다!" 나온다.

profile
하이도의 BackEnd 입문

0개의 댓글