colorful spring boot

spring boot의 banner / log 에 color 를 입혀보자

colorful spring boot

 

ASCII ART

문자 -> ASCII ART로 변환 : http://patorjk.com/software/taag/#p=display&f=Graffiti&t=Type%20Something%20

그림 -> ASCII ART로 변환 : http://picascii.com/

banner 변경

공식 문서 참조 : https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-spring-application.html

resources 디렉토리에 ASCII ART 로 디자인한 내용을 담은 banner.txt 파일을 놔두면 된다.

color 입히는 방법은 아래 예제를 참조

banner.txt
${AnsiColor.RED} ____   ____   ${AnsiColor.BLACK}    _________ __                          __
${AnsiColor.RED}|    | /  _/   ${AnsiColor.BLACK}   /   _____/|  |__   ____ ______ ______ |__| ____    ____
${AnsiColor.RED}|        <     ${AnsiColor.BLACK}   \_____  \ |  |  \ /  _ \\  __ \\  __ \|  |/    \  / __ \
${AnsiColor.RED}|    |    \    ${AnsiColor.BLACK}   /        \|   Y  (  <_> )  |_> >  |_> >  |   |  \/ /_/  >
${AnsiColor.RED}|____|\____\   ${AnsiColor.BLACK}  /_________/|___|__/\____/|   __/|   __/|__|___|__/\___  /
${AnsiColor.RED}               ${AnsiColor.BLACK}                           |__|   |__|             /_____/
${AnsiColor.BLACK} ...Running ${AnsiColor.BRIGHT_RED}Kshop sample application${AnsiColor.BRIGHT_BLACK}

 

log coloring

logback(spring boot default logger) 의 log encoder 를 설정하면 된다.

설정 방법은 아래 예제를 참조

logback.groovy
appender('console', ConsoleAppender) {
    encoder(PatternLayoutEncoder) {
        pattern = '%magenta(%d{yyyy-MM-dd HH:mm:ss.SSS}) %highlight(%5level) --- %yellow([%25thread]) %cyan(%logger{36}) - %msg%n'
    }
}
appender('rolling', RollingFileAppender) {
    file = "logs/daily.log"
    append = true
    rollingPolicy(TimeBasedRollingPolicy) {
        fileNamePattern = "/logs/daily.log.%d{yyyy-MM-dd}.gz"
        maxHistory = 10
    }
    encoder(PatternLayoutEncoder) {
        pattern = '%-5level %d{yyyy-MM-dd HH:mm:ss} [%thread] %logger{36} - %msg%n'
    }
}
root(INFO, ['console', 'rolling'])

spring boot

참고 문서

 

spring-boot

spring boot life-cycle

Cosysto Gimbh 라는 사람이 life-cycle에 대한 flow 를 아주 잘 그려놓았다.

 


 

실제 구현체 이름은 매우 길고 복잡하다. (e.g. ConfigurationWarningsApplicationContextInitializer)

spring application 내부에서 실제로 어떤 일이 일어나는지에 집중하도록 긴 이름은 짧게 축약하였다.

 

ApplicationContext 생성(=SpringApplication.run())

  • Listener 생성 및 시작
  • Environment 준비
  • Context 생성
  • Context 준비
    • Environment 세팅
    • 후처리(post process)
    • Initializer 초기화(apply initializer)
  • Context 로드
    • Source 로드
    • BeanDefinitionLoader 에 Source , Context 탑재
    • BeanDefinitionLoader 로드
  • 종료
    • Context Refresh

 

ApplicationContext 는 SpringApplication의 몸통에 해당하는 실제 instance의 추상화이다.

SpringApplication

SpringApplication 의 Entry point 는 반드시 특정 package를 지정하고, 그 하위에 위치시켜야 한다. (=Default package path에 entry point 시작 금지)

그렇지 않으면 ** WARNING ** : Your ApplicationContext is unlikely to start due to a @ComponentScan of the default package.  이 발생한다.

 

@SpringBootApplication 을 사용하면 다음 Annotation 을 생략 가능하다.

Source Code


@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Configuration
@EnableAutoConfiguration
@ComponentScan
public @interface SpringBootApplication {
/**
* Exclude specific auto-configuration classes such that they will never be applied.
* @return the classes to exclude
*/
Class<?>[] exclude() default {};
}

 

SpringApplicationRunListener

ConfigurableEnvironment

ConfigurableApplicationContext

 

실제로 호출되는 implementation 은 AnnotationConfigEmbddedWebApplicationContext 이다.

webapp framework 와 thread safety

아래 쓴 글 Servlet 의 동작방식과 thread safety 에서 응용편이다.

그러면 framework를 사용한 webapp 의 controller는 thread safe 할까?

결론부터 얘기하면 thread safe 하지 않다.

thread safe 한 코드를 짜기 위한 일반 원칙을 모두 지켜야 한다.

 

아래 참고사항은 잠들기 전 100번씩 암기해야 한다. 뭔말인지 모르겠어도 100번씩 외우고 자라.

thread safe 한 code 작성을 위한 일반 원칙

  1. thread 간 공유하는 resource가 존재하지 않는다.
  2. thread 간 공유하는 resource가 immutable 하다.
  3. thread 간 공유하는 resource가 mutable 하나, atomic 하다.
  4. thread 간 공유한느 resource가 mutable/non-atomic 하나, synchronized 하다.

 

spring이 되었든, struts 가 되었든, play 나 django가 되었든,

Servlet context 에 존재하는 resource를 접근하는 abstraction을 제공하게 되고, 여기서 접근되는 모든 데이터는 thread safe 하여야 한다.

당연히 framework 제작자는 상기 원칙대로 code를 생성하였기 때문에 framework는 멀티쓰레드 환경에서 안정적으로 동작하게 설계되어 있다.

항상 문제는 application(이라고 쓰고 bug라고 읽어라)을 만드는 개발자 놈들에게 있다.

 

아래 참고사항은 이닦기 전 1000번씩 암기해야 한다. 암기 못하면 이닦지 마라.

thread safe한 framework 코드 작성 원칙

  1. controller / action 등등등 Httprequest handler implementation에는 절대로 member variable 생성하지 마라.
  2. 만일 controller가 반드시 interface를 해야 할 일이 있다면, member variable 생성하는 대신 method parameter로 passing 하여 처리하라. 허나 좋은 설계를 하였다면 이런 일이 없어야 한다.
  3. 만일 2번으로 문제 해결이 되지 않는다면 너는 140% 잘못된 방향으로 개발을 하고 있다. 지금껏 니가 짠 코드를 지워라. 그게 프로젝트를 살리는 길이다.

요약

controller에 절대로 member 변수 만들지 마라.

servlet의 동작방식과 thread safety

 

3줄 요약

servlet, filter, listener 는 같은 어플리케이션 영역 안에 존재하며, 리소스를 공유한다.

상기 class 내의 member variable은 모두 thread safe 하지 않다.

맨 아래 예제처럼, local stack 내에서 parameter passing 하여 interface 하면 thread safe 하다.

ServletContext

When the servletcontainer (like Apache Tomcat) starts up, it will deploy and load all webapplications. When a webapplication get loaded, the servletcontainer will create the ServletContext once and keep in server’s memory. The webapp’s web.xml will be parsed and every <servlet><filter> and <listener> found in web.xml, or annotated with respectively @WebServlet@WebFilter and @WebListener, will be created once and kept in server’s memory as well. For all filters, the init() method will also be invoked immediately. When the servletcontainer shuts down, it will unload all webapplications, invoke the destroy() of all initialized servlets and filters, and finally the ServletContext and all ServletFilter and Listener instances will be trashed.

When the Servlet in question has a <servlet><load-on-startup> or @WebServlet(loadOnStartup) value greater than 0, then its init() method will also immediately be invoked during startup. Those servlets are initialized in the same order as “load-on-startup” value represents, or if they are the same, then the order in the web.xml or @WebServletclassloading. Or, if the “load-on-startup” value is absent, then the init() method will only be invoked on very first HTTP request hitting the servlet in question.

HttpServletRequest and HttpServletResponse

The servletcontainer is attached to a webserver which listens on HTTP requests on a certain port number, which is usually 8080 in development and 80 in production. When a client (user with a webbrowser) sends a HTTP request, the servletcontainer will create new HttpServletRequest and HttpServletResponse objects and pass it through the methods of the already-created Filter and Servlet instances whose url-pattern matches the request URL, all in the same thread.

In case of filters, the doFilter() method will be invoked. When its code calls chain.doFilter(request, response), then the request and response will continue to the next filter, or if there is none, hit the servlet. In case of servlets, the service() method will be invoked, which by default determines based on request.getMethod() which one of the doXxx() methods to invoke. If such method is absent on the actual servlet, then it will return HTTP 405 error.

The request object provides access to all information of the HTTP request, such as the request headers and the request body. The response object provides facility to control and send the HTTP response the way you want, such as setting headers and the body (usually with HTML content from a JSP file). When the HTTP response is committed and finished, then both the request and response objects will be trashed (actually, most containers will cleanup the state and recycle the instance for reuse).

HttpSession

When a client visits the webapp for the first time and/or the HttpSession is to be obtained for the first time by request.getSession(), then the servletcontainer will create a new HttpSessionobject, generate a long and unique ID (which you can get by session.getId()), and store it in server’s memory. The servletcontainer will also set a Cookie in the Set-Cookie header of the HTTP response with JSESSIONID as cookie name and the unique session ID as cookie value.

As per the HTTP cookie specification (a contract a decent webbrowser and webserver has to adhere), the client (the webbrowser) is required to send this cookie back in the subsequent requests in the Cookie header as long as the cookie is valid. Using browser builtin HTTP traffic monitor you can check them (press F12 in Chrome / Firefox23+ / IE9+ and check Net/Network tab). The servletcontainer will determine the Cookie header of every incoming HTTP request for the presence of the cookie with the name JSESSIONID and use its value (the session ID) to get the associated HttpSession from server’s memory.

The HttpSession lives until it has not been used for more than the <session-timeout> time, a setting you can specify in web.xml, which defaults to 30 minutes. So when the client doesn’t visit the webapp anymore for over 30 minutes, then the servletcontainer will trash the session. Every subsequent request, even though with the cookie specified, will not have access to the same session anymore. The servletcontainer will create a new one.

On the other hand, the session cookie on the client side has a default lifetime which is as long as the browser instance is running. So when the client closes the browser instance (all tabs/windows), then the session will be trashed at the client side. In a new browser instance the cookie associated with the session won’t be sent anymore. A new request.getSession() would return a brand new HttpSession and set a cookie with a brand new session ID.

In a nutshell

  • The ServletContext lives as long as the webapp lives. It’s been shared among all requests inall sessions.
  • The HttpSession lives as long as the client is interacting with the webapp with the same browser instance and the session hasn’t timed out at the server side yet. It’s been shared among all requests in the same session.
  • The HttpServletRequest and HttpServletResponse lives as long as the client has sent it until the complete response (the webpage) is arrived. It is not being shared elsewhere.
  • Any ServletFilter and Listener lives as long as the webapp lives. They are being shared among all requests in all sessions.
  • Any attribute which you set in ServletContextHttpServletRequest and HttpSession will live as long as the object in question lives. The object itself represents the “scope” in bean management frameworks such as JSF, CDI, Spring, etc. Those frameworks store their scoped beans as an attribute of closest matching scope.

Threadsafety

That said, your major concern is possibly threadsafety. You should now have learnt that Servlets and filters are shared among all requests. That’s the nice thing of Java, it’s multithreaded and different threads (read: HTTP requests) can make use of the same instance. It would otherwise have been too expensive to recreate, init() and destroy() it on every single request.

But you should also realize that you should never assign any request or session scoped data as aninstance variable of a servlet or filter. It will be shared among all other requests in other sessions. That’s threadunsafe! The below example illustrates that:

public class ExampleServlet extends HttpServlet {
private Object thisIsNOTThreadSafe;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Object thisIsThreadSafe;

thisIsNOTThreadSafe = request.getParameter("foo"); // BAD!! Shared among all requests!
thisIsThreadSafe = request.getParameter("foo"); // OK, this is thread safe.
}
}