Embedded Tomcat (WAS, Servlet Container) 제공
의존성 관리
@SpringBootApplication 어노테이션으로 xml 설정 대체
스프링부트는 Bean 을 두단계로 등록한다.
ContextLoaderListener / DispatcherServlet / CharacterEncodingFilter 설정은 Spring Boot 에서는 추가하지 않아도 된다.
독립실행 가능한 어플리케이션 지향 (JAR도 독립실행 가능)
의존성 관리
임베디드 톰캣 (독립 실행 가능한 어플리케이션)
Auto Configuration (@EnableAutoConfiguration)
Spring Boot Auto Configuration 은 추가된 jar 의존성을 기반으로 Spring application을 자동적으로 설정하는 것을 시도한다. 예를 들어 HSQLDB 가 클래스패스에 있다면 어떤 데이터베이스 연결 빈을 정의하지 않아도 자동적으로 in-memory 데이터베이스에 접근할 것이다.
어노테이션 기반 Component Scan (@Component)
현재 패키지 이하에서, @Component 어노테이션이 붙은 클래스들을 찾아 Bean 으로 등록한다. (제외도 가능)
자바를 사용하여 웹페이지를 동적으로 생성하는 서버측 프로그램 혹은 그 Spec
자바 서블릿은 웹 서버의 성능을 향상하기 위해 사용되는 자바 클래스의 일종이다.
자바로 구현된 CGI 라고 말할 수 있다.
클라이언트가 HTTP 요청시 이를 Servlet Container (WAS) 로 전송
이를 전송받은 Servlet Container 는 *HttpServletRequest, HttpServletResponse* 의 객체들을 생성한다.
web.xml 은 사용자가 요청한 URL을 분석하여 어느 서블릿에 대해 요청을 한 것인지 찾습니다. (한개의 서블릿 당 한개의 컨트롤러 사상)
해당 서블릿 스레드 (객체 아님) 에서 service 메소드를 호출한 후 요청의 POST, GET 여부에 따라 doGet() 또는 doPost()를 호출
doGet() or doPost() 메소드는 동적 페이지를 생성한 후 HttpServletResponse 객체로 응답.
응답이 끝나면 HttpServletRequest, HttpServletResponse 소멸
서블릿 컨테이너는 서블릿을 관리하며 네크워크 통신, 서블릿의 생명주기 관리, 스레드 기반의 병렬처리를 대행한다. 가장 대표적인 것으로는 아파치 톰캣(Tomcat)이 있다.
웹서버 는 클라이언트의 요청을 기다리고 요청에 대한 데이터를 만들어서 응답하는 역할을 한다. 이때 데이터는 정적인 데이터(html, css, 이미지등)로 한정된다. 말그대로 미리 정해진 자원들을 응답해 주는 것이다.
WAS 클라이언트의 요청이 있을 때 *내부 프로그램을 통해 결과를 만들어내고* 이것을 다시 클라이언트에게 돌려주는 역할을 한다. 이는 WS의 기능 일부와 웹 컨테이너의 결합으로 다양한 기능을 컨테이너에 구현하여 다양한 역할을 수행한다.
예전에는 static file 은 웹서버를 통해 제공하고, 동적인 기능들만 WAS 를 쓰는것이 효율적이라는 말이 있었는데, 톰캣은 5.5부터 Httpd 의 native모듈을 사용해서 스태틱파일을 처리하는 기능을 제공한다.
이 경우 아파치와 톰캣이 같은 모듈을 사용하는 셈이니 성능에서 차이가 날 이유가 없다. 실제 테스트 한 결과를 봐도 톰캣에서 아파치 Native 모듈을 사용하는 것이 순수하게 아파치 Httpd만 사용하는 것과 비교해서 성능이 전혀 떨어지지 않는다.
이는 웹 서버 어플리케이션의 발전속에 응답의 표현을 위해 자연스럽게 등장했다.
기존 서블릿 같은 경우 HttpServletResponse 객체에게 응답 html을 주려면 아래와 같이 상당히 귀찮은 작업이 필요했다.
public class HelloServlet extends HttpServlet {
public void doGet(HttpServletRequest req,
HttpServletResponse res)
throws ServletException,IOException {
res.setContentType("text/html;
charset=UTF-8");
PrintWriter out = res.getWriter();
out.println("<HTML>");
out.println("<BODY>");
out.println("Hello World!!");
out.println("</BODY>");
out.println("</HTML>");
out.close();
}
}
그래서 등장한 도구가 JSP (루비의 ERB 같은 느낌이라고 보면 된다)
<% @page import="java.util.Calendar" %>
<% @page contentType="text/html; charset=UTF-8"%>
<% String str = String.format("%tF",Calendar.getInstance()); %> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type"
content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
오늘은 <%= str %><br/> 한시간만 참으면 점심....
</body>
</html>
아무튼 이렇게 기존 방식보다 html 작성이 훨씬 빠르게 되었다.
내부적으로는 JSP 파일은 Tomcat이 Servlet으로 바꾸어서 돌린다고 한다.
컨테이너는 보통 인스턴스의 생명주기를 관리하며,
생성된 인스턴스들에게 추가적인 기능을 제공하도록 하는 것이다.
컨테이너는 개발자가 작성한 코드의 처리과정을 위임받은 존재이다.
예를들어 서블릿 컨테이너는 서블릿을 생성하고 스레드를 이용해 사용자의 요청을 처리한다.
이와 마찬가지로, 스프링에도 의존성 주입을 이용하여 어플리케이션 컴포넌트들을 관리하는 컨테이너가 있다. 이것이 바로 Spring Container 이다.
그리고 컨테이너는 아래와 같은 특징을 가진다
IOC (Inversion Of Control - 제어의 역전)
개발자는 보통 객체지향 프로그래밍을 하면 New 연산자, 인터페이스 호출, 팩토리 호출방식으로 객체를 생성하고 소멸시킨다.
IOC란 이렇게 원래 개발자의 의무인 인스턴스의 생성부터 소멸까지의 객체 생명주기 관리를 컨테이너가 해 주는것을 말한다.
DI (Dependency Injection - 의존성 주입)
IoC를 실제로 구현하는 방법으로서 의존성있는 컴포넌트들 간의 관계를 개발자가 직접 코드로 명시하지 않고 컨테이너인 Spring이 런타임에 찾아서(getBean) 의존성을 주입해 주는 것이다.
BeanFactory
DI의 기본사항을 제공하는 가장 단순한 컨테이너이다.
BeanFactory 는 빈을 생성하고 분배하는 책임을 지는 클래스
빈 자체가 필요하게 되기 전까지는 인스턴스화를 하지 않는다 (lazy loading)
ApplicationContext
빈팩토리와 유사한 기능을 제공하지만 좀 더 많은 기능을 제공한다.
https://minwan1.github.io/2017/10/29/2017-10-29-Spring-AOP-Proxy/
모듈을 쉽게 사용할 수 있도록 해주는 것이라고 보는것이 편함
특정 로직이 수평으로 흐른다면, 수직으로 원하는 흐름을 꽂아주는 방식이다.
핵심로직을 구현한 코드를 컴파일하거나, 컴파일된 클래스를 로딩하거나, 또는 로딩한 클래스의 객체를 생성할 때, Proxy 객체를 통해 호출할 때 AOP가 적용됨
1,2 번 같은 경우는 AspectJ라이브러리를 추가하여 구현할때 사용됨.
런타임시 위빙같은 경우는 Spring-AOP 에서 사용하는 방식으로, 소스코드나 클래스 자체를 변경하는 것이 아니라 프록시 객체 를 사용하여 모듈을 Weaving 하고 비즈니스 로직을 실행한다.
*Proxy 를 이용한 런타임 위빙*
스프링은 자체적으로 프록시 기반의 런타임 AOP를 지원하고 있다.
따라서 11-2 에서 살짝 설명한 것과 같이,
Spring-AOP는 *메서드 호출* Joinpoint 만을 지원함.
만약에 *필드값 변경* 과 같은 Joinpoint 를 사용하고 싶다면, AspectJ와 같이 다양한 Joinpoint를 지원하는 AOP프레임워크를 사용해야 한다.
또한 스프링은 3가지 방식으로 AOP를 제공한다.
어떠한 방식을 사용하더라도 Proxy를 통한 메서드 호출만 AOP가 적용된다는것을 알아두자
어쨌든 실행해야 하는 비즈니스 로직이 있는 클래스에 대해 무조건 Bean 객체가 생성되는데, 스프링 컨테이너가 지정한 Bean 객체에 대한 프록시 객체를 생성하고, 원본 Bean 객체 대신에 Proxy 객체를 사용하게 된다. 이때 프록시 객체를 생성하는 방식은 인터페이스 여부에 따라 두가지 방식으로 나뉜다.
스프링의 @Transactional annotation 은 Proxy Mode 와 AspectJ 모드로 동작한다.
일반적으로 많이 사용되는 것은 Proxy Mode 인데, 아무래도 Bean 이 아니라 프록시 객체를 이용하는 방법이다 보니, 의도치 않게 @Transactional 어노테이션이 동작하지 않는 사례들이 있다.
private method에 @Transactional 달았을때
Service Bean 에서 메서드를 호출하는 것이 아니라, Proxy 객체가 메서드를 호출하게 되므로 당연히 @Transactional 어노테이션이 적용되지 않는다.
이 부분은 SonarLint 같은 정적 코드분석기도 알려주는 부분이라 발견하기 쉽다.
메서드 안에서 내부 메서드 call
self call 에 관한 부분이다.
내부 클래스에서의 동일 메소드를 호출 할 때에는 Proxy 객체가 아닌 this 를 이용해 메소드를 호출하기 때문이다. 프록시로 특정 메서드를 불러냈어도 그 메서드 흐름 안의 internal method 는 Bean 이 호출한다.
따라서 프록시 객체에 의해 동작하는 Spring-AOP는 내부 method를 호출하는지 까지는 관심이 없다. 처음 프록시로 불러낸 메서드까지가 Proxy 관할 구역임
아래와 같은 코드는 무용지물이다.
프록시는 자신이 호출한 메서드 그 하나만 인식하는 것이다.
@Transactional
public void cancel(String orgFlwNo) {
cancelTransaction(orgFlwNo);
}
public void cancelTransaction(String orgFlwNo) {
TradeRepository.cancelTrade(orgFlwNo);
userPointRepository.savePoint(CURRENT_USER_ID)
}
당연히 아래와 같은 코드도 무용지물이다.
어차피 프록시가 호출하지 않을 메서드이기 때문 (태어날때부터 self call이 예견되어 있다.)
public void cancel(String orgFlwNo) {
cancelTransaction(orgFlwNo);
}
@Transactional
public void cancelTransaction(String orgFlwNo) {
TradeRepository.cancelTrade(orgFlwNo);
userPointRepository.savePoint(CURRENT_USER_ID)
}
헷깔릴 수도 있는데, 프록시는 메서드를 불러주는 대리인이다.
대리인은 말그대로 다른 클래스에서 호출하는 것과 마찬가지다.
위 코드에서 cancel 메서드는 외부에서 부르게 되는 시나리오의 메서드이고, 아래 cancelTransaction은 cancel 내부에서 호출하고 실행해야 의미가 있는 내부 메서드이다. (위의 예제는 임시로 12.1 의 이유로 public scope로 되어 있음)
따라서 proxy 입장에서 내부에서 호출해야만 하는 메서드에 아무리 @Transactional 을 달아봐야 외부인인 proxy 는 내부 메서드 cancelTransaction 의 흐름을 가로챌 기회가 없는 것이다.
Try Catch 구문 안에서 사용할때
@Transactional 어노테이션은 비즈니스 로직 실행 뒤 Exception을 인식하고 처리한다.
따라서 로직 안에서 try - catch 를 해버리면 @Transactional이 예외를 인식하기도 전에 메서드 내부에서 예외를 catch 하게 되어 예외시 ROLLBACK이 불가능하다.
웹 어플리케이션이 실행되면 Tomcat(WAS) 에 의해 배포서술자 web.xml
로딩
WAS 는
web.xml
에 등록되어 있는 ContextLoaderListener 생성
root-context.xml
을 로딩
root-context.xml
은 Service , Repository 와 같은 bean 을 설정함Root Spring Container 구동
root-context.xml
의 설정 기반으로 Root Spring Container 구동사용자 요청 받음
두번째 Spring Container 구동 (By DispatcherServlet)
WEB-INF/config
폴더에 있는DispatcherServlet 의
doService()
호출
doService()
에는 사용자의 요청을 처리하기 위한, Handler 과 Adapter 을 찾아 호출하기 위해 doDispatch()
호출
doDispatch()
에서는 HandlerMapping 을 통해서 요청에 맞는 Controller 의 HandlerMethod 를 찾고, HandlerMethod 를 호출할 수 있는 HandlerAdapter 찾음
HandlerAdapter 가 HandlerMethod 호출
Controller 요청 처리 후, Controller 는 ModelAndView , View 리턴
리턴받은 View 는 ViewResolver가 먼저 받아 해당 view가 존재하는지 검색
DispatcherServlet은 ViewResolver 로 부터 JSP 등 최종 표시 결과를 받아서 최종 결과를 사용자에게 전송
Persistence Framework 는 두가지가 있다.
SQL <- mapping -> Object 필드
대표적인 예로는 Mybatis 이고, 실제 SQL을 명시해 주어야 한다.
직접 SQL을 많이 만져야 하는 쿼리가 있는 경우에 좋다.
XML 파일에 쿼리 쓰고 @Autowired 하는 방식
SQL Mapper의 Query는 xml로 관리되어지기 때문에 유지보수 과정에서 문제가 발생했습니다. 빌드 과정에서 오류를 발생시키지 않기 때문에 최악에는 SQL Query과 관련된 모든 Controller/Business/DAO Layer를 찾아서 유지보수 해야했습니다.
class < jar < war < ear
java class 파일 및 리소스파일(이미지, 텍스트 등)의 컨테이너이다.
jar 파일이 독립실행 되려면, 클래스 중 하나가 기본 클래스로 정의된다.
실행 가능한 jar 파일은 아래 명령어로 실행된다. (spring 지원)
java -jar foo.jar
웹 서버 위에서 다른 WAS 과 함께 운영해야 한다면 외장 톰캣
아니면 기존 레거시가 있거나 ..
그럴필요 없다면 내장 톰캣을 써도 좋다.
D2 Naver 의 동영상 플랫폼 개발 팀도 embed 톰캣을 쓰더라
https://d2.naver.com/helloworld/5626759
https://zepinos.tistory.com/51
스프링은 LogBack, Log4j 등 여러 로그 라이브러리를 지원한다.
Logback 이 Log4j 보다 10배이상 빠르고 메모리 효율성도 좋단다.
\1) FATAL : 가장 크리티컬한 에러 2) ERROR : 에러 3) WARN : 에러는 아니지만 주의할 것 4) INFO : 일반 정보 5) DEBUG : 일반 정보를 좀 더 상세히 (옵션을 켜야 보임) 6) TRACE : 에러 시 경로 추적 (스택 트레이스 같은 것)
둘다 빌드 툴이다.
라이브러리 Dependency 를 관리한다.
Gradle 이 더 좋아보임 (출시 시기도 늦고 ANT, MAVEN 장점만을 모았다고 함)
스프링부트는 처음 빌드 설정파일에 spring-boot-starter-test 넣으면 다 설치됨.
아래와 같은 것들이 포함된다고 한다.