본문 바로가기

Spring Boot

인프런_토비의 스프링부트_정리본_20230606

스프링 부트란? 스프링을 기반으로 실무 환경에서 사용 가능한 수준의 독립실행형 애플리케이션을 복잡한 고민없이 빠르게 작성할 수 있게 도와주는 여러가지 도구의 모음.

 

1. 스프링 != 스프링 부트

2. 독립실행형(stand-alone) 어플리케이션

  • Tomcat, Jetty, Undertow 같은 웹어플리케이션서버(WAS)를 자체 내장하고 있다. (톰캣-default)
  • 다양한 어플리케이션 구성에 따라서 다양한 스타터(“starter”) 모듈들을 선택할 수 있다. (주로 web-starter)
  • 스프링 및 외부 라이브러리의 설정을 자동으로 해준다. (강한 주장을 가진(opinionated) 도구)

*** 스프링 프레임워크의 설계 철학

  1. 극단적인 유연함 추구
  2. 다양한 관점을 수용
  3. NOT opinionated
  4. 수많은 선택지를 다 포용
  5. 하지만 수많은 선택지를 개발자들이 다 선택해야하는 불편함, 어려움 존재

***스프링부트의 설계 철학

1. 일단 정해주는 대로 빠르게 개발 시작

2. 어떤 종류, 버전을 선텍해야할지 하는 수고스러움을 덜어줌.

3. 각 기술을 스프링에 적용하는 방식(DI 구성)과 디폴트 설정값 제공

4. 단점도 존재함. 호환성 문제 등 커스텀마이징에 어려움.

하지만 내장된 디폴트 구성을 커스터마이징 하는 매우 자연스럽고 유연한 방법 제공

 

* 스프링부트 사용의 이점

스프링부트는 스프링프레임워크 애플리케이션을 사용하여 빠른 개발을 가능하도록 도와준다. 스프링부트를 사용하지 않고 스프링 프레임워크로 개발을 시작하려하면 실제 개발 코드를 작성하기도 전에 지치게 될 것이다. 왜냐하면 각종 dependency Injection 과정이 필요하고 서버설정, 버전에 따른 호환성 체크 등 다수의 성가신 작업이 존재하기 때문이다. 그리고 보통 이러한 작업들은 아마도 검색(구글링...)을 통해 그때그때 진행하게 될 것이다. 즉, 필요한 설정을 완료하게 된다면 그 과정은 자연스레 잊혀지게 된다. 일련의 작업의 효용성이 없다.

-> 'Opinionated' (독선적인, 자기주장이 강한) -> "내가 다 정해줄게 일단 개발만 해."

  • 스프링부트를 이해하게 되면
    1. 스프링부트가 스프링의 기술을 어떻게 활용하는지 배우고 응용할 수 있다.
    2. 부트가 선택한 기술, 자동으로 만들어주는 구성, 디폴트 설정이 어떤 것인지 확인할수 있다.

==========================================================================================

*** 독립실행이 가능한 서블릿컨테이너 만들기

 

우선 스프링부트에서 서블릿 컨테이너를 만드는 간단한 작업 부터 시작

package tobyspring.helloboot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class HellobootApplication {

    public static void main(String[] args) {
        SpringApplication.run(HellobootApplication.class, args);
    }
}

스프링부트 프로젝트 생성시 존재하는 @SpringBootApplication 어노테이션 제거.

톰캣을 불러서 사용하는 코드 작성 후 동작하는지 확인

public class HellobootApplication {

    public static void main(String[] args) {
        TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
                                 WebServer webServer =  factory.getWebServer(servletContext -> {
                                     servletContext.addServlet("hello", new HttpServlet(){
                                         @Override
                                         protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
                                             resp.setStatus(200);
                                             resp.setHeader("Content-Type", "text/plain");
                                             resp.getWriter().println("HELLO SERVLET");
                                         }
                                     }).addMapping("/hello");
                                 });
                                 webServer.start();
    }
}

 

 

DispatcherServlet 의 사용으로 간략해진 코드.

public class HellobootApplication {

public static void main(String[] args) {
/* 스프링 컨테이너를 코드로 등록 */
GenericWebApplicationContext applicationContext = new GenericWebApplicationContext();
applicationContext.registerBean(HelloController.class);
applicationContext.registerBean(SimpleHelloService.class);
applicationContext.refresh(); //초기화

/* 서블릿 컨테이너를 코드로 실행하면서 서블릿을 등록하는 작업 */
ServletWebServerFactory serverFactory = new TomcatServletWebServerFactory();
WebServer webServer = serverFactory.getWebServer(servletContext -> {
servletContext.addServlet("dispatcherServlet",
new DispatcherServlet(applicationContext)
).addMapping("/*");
});
webServer.start();
}
}

 

 

나눠진 작업을 'onRefresh' 라는 메서드를 사용해서 스프링컨테이너 초기화중에 서블릿컨테이너를 등록하고 실행하도록 통합하였다.

GenericWebApplicationContext applicationContext = new GenericWebApplicationContext() {
@Override
protected void onRefresh() {
super.onRefresh();

/* 서블릿 컨테이너를 코드로 실행하면서 서블릿을 등록하는 작업 */
ServletWebServerFactory serverFactory = new TomcatServletWebServerFactory();
WebServer webServer = serverFactory.getWebServer(servletContext -> {
servletContext.addServlet("dispatcherServlet",
new DispatcherServlet(this)
).addMapping("/*");
});
webServer.start();
}

};

============================================================================================

 

*** 빈을 등록하는 방법

 

1. @Configuration @Bean 을 사용해 빈을 등록

@Configuration
public class HellobootApplication {
@Bean
public HelloController helloController(HelloService helloService) {
return new HelloController(helloService);
}
@Bean
public HelloService helloService() {
return new SimpleHelloService();
}

 

2. 더 일반적인 방법으로는 @Component 을 사용해서 빈으로 등록하고 @ComponentScan 을 이용해서 빈으로 등록된 클래스를 스캔하는 방법

@Configuration
@ComponentScan
public class HellobootApplication {
@RequestMapping
@Component
public class HelloController {
@Component
public class SimpleHelloService implements HelloService {

관리의 용이성 측면으로 봤을때는 첫번째 방식이 유리하다.

============================================================================================

아래 2개의 Class를 보면 스프링부트 프로젝트를 시작했을 때 모습과 비슷해졌다는것을 알 수 있다

public class MySpringApplication {
public static void run(Class<?> applicationClass, String... args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext() {
@Override
protected void onRefresh() {
super.onRefresh();

ServletWebServerFactory serverFactory = this.getBean(ServletWebServerFactory.class);
DispatcherServlet dispatcherServlet = this.getBean(DispatcherServlet.class);

WebServer webServer = serverFactory.getWebServer(servletContext -> {
servletContext.addServlet("dispatcherServlet", dispatcherServlet)
.addMapping("/*");
});
webServer.start(); //서블릿컨테이너 실행
}
};
applicationContext.register(applicationClass);
applicationContext.refresh(); //스프링컨테이너 초기화
}
}
@Configuration
@ComponentScan
public class HellobootApplication {
@Bean
public ServletWebServerFactory servletWebServerFactory() {
return new TomcatServletWebServerFactory();
}

@Bean
public DispatcherServlet dispatcherServlet() {
return new DispatcherServlet();
}

public static void main(String[] args) {
MySpringApplication.run(HellobootApplication.class, args);
}

}

 

@MySpringBootAnnotation 어노테이션을 생성하고 빈으로 등록되어 있던 ServletWebServerFactory 와 DIspatcherServlet 을 Config 클래스에서 빈으로 정의해주게 되면 최초 스프링부트 프로젝트 생성시 있던 @SpringBootAnnotation 을 사용한 모습과 같아진 것을 확인할 수 있다.

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Configuration
@ComponentScan
public @interface MySpringBootAnnotation {
}
@Configuration
public class Config {

@Bean
public ServletWebServerFactory servletWebServerFactory() {
return new TomcatServletWebServerFactory();
}

@Bean
public DispatcherServlet dispatcherServlet() {
return new DispatcherServlet();
}
}

 

@MySpringBootAnnotation
public class HellobootApplication {

public static void main(String[] args) {
SpringApplication.run(HellobootApplication.class, args);
}

}

아래는 스프링부트 프로젝트 생성시 기존 코드

package tobyspring.helloboot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class HellobootApplication {

    public static void main(String[] args) {
        SpringApplication.run(HellobootApplication.class, args);
    }
}

============================================================================================

***@Conditional - 스프링4.0에 추가된 애노테이션으로 모든 조건을 만족하는 경우에만 컨테이너에 빈으로 등록되도록 함.

org.springframework.boot.autoconfigure 안에는 많은 설정이 있다.(약 114가지 자동구성 설정정보 존재)

서블릿컨테이너는 대표적으로 Tomcat, Jetty, Undertow 가 있는 것을 확인할 수 있는데 @Conditional 어노테이션을 사용해서 서블릿컨테이너의 상태에 따라 원하는 서블릿컨테이너를 선택해서 사용할 수 있다.

@MyAutoConfiguration
@Conditional(TomcatWebServerConfig.TomcatContion.class)
public class TomcatWebServerConfig {
@Bean("tomcatWebServerFactory")
public ServletWebServerFactory servletWebServerFactory() {
return new TomcatServletWebServerFactory();
}

static public class TomcatContion implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return false;
}
}
}
@MyAutoConfiguration
@Conditional(JettyWebServerConfig.JettyCondition.class)
public class JettyWebServerConfig {
@Bean("jettyWebServerFactory")
public ServletWebServerFactory servletWebServerFactory() {

return new JettyServletWebServerFactory();
}

static class JettyCondition implements Condition {

@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return true;
}
}
}

 

 

@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return ClassUtils.isPresent("org.eclipse.jetty.server.Server", context.getClassLoader());
}

참고) ClassUtils의 isPresent 메서드를 사용해서 라이브러리 존재여부를 체크할수 있음.

 

주의할 점 : 사용자 구성정보를 통해 자동구성정보를 대체해서 사용할 수 있지만 사용자 구성정보가 스프링부트의 자동구성 정보보다 우선하기 때문에 그에 따른 파급효과를 인지하고 사용해야한다.

 

===========================================================================================

*** DataSource

서블릿컨테이너와 마찬가지로 @Conditional 어노테이션을 이용해서 컨디션에 따른 DataSource 선택이 가능하다.

주로 Hikari를 사용한다. (Hikari는 connection pooling을 제공하는 JDBC DataSource의 구현체)

@Bean 
@ConditionalOnMissingBean
DataSource hikariDataSource(MyDataSourceProperties properties) {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setDriverClassName(properties.getDriverClassName());
dataSource.setJdbcUrl(properties.getUrl());
dataSource.setUsername(properties.getUsername());
dataSource.setPassword(properties.getPassword());
return dataSource;
}

============================================================================================

지금까지 스프링 부트의 자동구성을 직접 구현해보고, 스프링 부트의 자동구성 대신 사용자구성으로 대체하는 방법에 대해 알아보았다.

 

***  스프링부트의 자동구성후보 확인 방법

VMoption으로-Ddebug 추가

VMoption으로-Ddebug 추가을 추가하고 APP실행을 하면 CONDITIONS EVALUATION REPORT 를 확인할 수 있다.(자동 구성 후보들)

build.gradle파일에 코어 - web - jdbc 를 추가했을때 각각 어떠한 자동구성정보들이 추가되는지 확인해볼 수 있다.

============================================================================================

 

*** 마무리

위 그림에서 볼 수 있듯이 개발자는 단순하게 스프링부트 스타터에서 필요한 Dependency만 선택하기만 하면 스프링부트는 자동설정해주는 것을 확인할 수 있다. 복잡하고 머리 아픈 작업을 스킵하고 개발자는  @ComponentScan 애노터이션을 통해 @component로 애플리케이션 로직 빈만 작성하면 된다. 물론 @Configuration 애노테이션을 통해 커스텀 인프라 빈을 등록해서 사용할 수도 있다. 확장성 또한 좋기 때문에 개발자 입장에서 스프링부트는 정말 환영할 만한 기술이라고 할 수 있다.

이번 학습을 통해서 스프링부트가 스프링프레임워크와 무엇이 다른지 알 수 있었고, 스프링부트의 설계 철학을 조금이나마 체감할 수 있었다. 추가적으로 스프링부트의 기반이 되는 스프링 프레임워크의 동작원리를 이해하기 위한 지속적인 학습의 필요성도 느끼게 되었다.

'Spring Boot' 카테고리의 다른 글

스프링부트학습_20230604_2  (0) 2023.06.04
20230604_스프링부트학습  (0) 2023.06.04
20230528_스프링부트학습  (0) 2023.05.28
20230526_스프링부트 학습  (0) 2023.05.26
20230521_스프링부트 학습  (0) 2023.05.21