Chapter 4 스프링 부트 웹 개발 

 

1. 스프링 부트에 대한 이해

스프링을 사용할 떄는 다양한 모듈을 설정해야 하는 데, 이러한 설정은 처음 시작하는 입장에서 부담스러움 

-> 스프링 부트 등장 

 

1) 스프링 부트의 프로젝트 레이아웃

빌드 결과 파일 모맷에 따른 폴더 규격 구분 

- 결과 파일 포맷은 크게 WAR와 JAR 2가지로 구분할 수 있다.  

-> 프로젝트 파일을 압축한 것

-> 각 압축 파일은 내부 폴더 규격이 다름 

- WAR : WEB-INF 폴더를 기준으로 자바 클래스 폴더,, src/main/webapp 폴더에 웹 정적 자원이 위치함

 

스프링 부트에서 웹 자원을 실행하기 위한 규약 

- 정적 HTML 파일 

: src/main/resources/static

: src/main/public

- 웹 페이지 대표 아이콘 

: src/main/resources/favicon.ico

- 템플릿 

: src/main/resources/templetes

html - Thymeleaf

tpl - Groovy

ftl - Freemarker

vm - velocity

 

webJAR를 이용한 외부 프론트앤드 라이브러리 관리 

- booststrap, jQuery, AngularJs 등 많은 라이브러리를 사용함 

- webJAR를 이용하면 JAR 형태로 메이븐이나 그래들로 관리 가능

 

2) 스프링 부트 실행하기 

책에서는 spring-boot-cli를 이용하여 스프링 부트를 실행하는 방법을 설명하고 있다. 

일반적으로는 intellij나 eclipse를 이용하여 스프링 부트를 실행하기 때문에 넘어간다. 

또한, build.gradle 역시 Spring Initializr를 이용하면 쉽게 작성할 수 있다. (아래의 링크)

https://start.spring.io/

 

컨트롤러 작성

@RestController
public class HomeController {
    @RequestMapping("/")
    public String hello() {
        return "hello";
    }
}

RestController 어노테이션을 사용하면 컨트롤러 응답 자체가 body가 되므로 별도로 뷰 페이지를 작성하지 않고 설정이 제대로 되었는지 확인할 수 있다. 

 

메인함수 

@SpringBootApplication
public class UIMain {
    public static void main(String ar[]){
        SpringApplication.run(UIMain.class, ar);
    }
}

스프링부트는 JAR 형태로 동작하기 때문에 main 메서드를 이용하여 실행할 수 있다. 

main 메서드가 실행되면 내장된 톰캣에 의해 웹페이지가 실행된다. 

@SpringBootApplication 은 @ConponentScan, @Configuration, @EnableAutoConfiguration 어노테이션의 기능을 포함하는 어노테이션이다. 

  • @ConponentScan : 컨트롤러 클래스들을 읽을 수 있도록 스캘 패키지를 지정함
  • @Configuration :  설정임을 나타냄
  • @EnableAutoConfiguration : 미리 정의된 Configuration 클래스를 읽을 수 있도록한다.

gradlew bootRun을 cmd에 입력하면, 내장된 톰캣이 함께 실행되고, WebApplicationContext가 초기화된다. 

 

 

스프링부트가 로드하는 빈을 확인하려면 SpringApplication.run 메서드의 반환값을 ApplicationContext에 대입하여 확인할 수 있다. 

@SpringBootApplication
public class UIMain {
    public static void main(String ar[]){
    	ApplicationContext ctx = SpringApplication.run(SpringLoadBeans.class,ar);
        String[] beanNames = ctx.getBeanDefinitionNames();
        Arrays.sort(beanNames);
        for(String beanName : beanNames)
        	System.out.println(beanName);
	}
}

 


2. 정적 자원 관리

1) 정적 자원 기본 설정 

정적 자원 : html, css, image, javascript 등 컴파일이 필요없는 파일 

 

웹 리소스 폴더

- /META-INF/resources, /static, /public 이 3개의 폴더를 웹 리소스 폴더라고 부른다. 

- 템플릿 엔진 의존성을 클래스패스에 추가하면 스프링 부트에서는 자동으로 src/main/resources/templates 경로를 기본 경로로 인식함 

 

이미지 URL 접근하기 

WebMvcAutoConfiguration 클래스를 보면 string 배열으로 웹 리소스 폴더 경롣르이 지정되어 있음

/src/main/resources/static 경로에 images 폴덜르 만들고 이미지를 추가한다. 

localhost:8080/images/이미지 파일명으로 접근하면 추가한 이미지 파일을 확인할 수 있다. 

 

2) 웹 리소스 폴더 설정 

static이나 public 폴더 이외에 다른 폴더를 지정하거나 별도의 리졸버를 추가하거나 캐시 설정을 변경하는 등의 작업 

-> WebMvcConfigureAdapter 클래스의 addResourceHandlers 메서드를 오버라이드 

 

별도의 리졸버를 추가하기 위해 WebConfig 파일을 작성한다. 

@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        boolean devMode = this.env.acceptsProfiles("dev");
        boolean useResourceCache = !devMode;
        Integer cachePeriod = devMode ? 0 : null;

        registry.addResourceHandler("/assets/**")
                .addResourceLocations("classpath:/assets/", "/assets/");
    }
}

이때 addResourceHandlers를 오버라이드 하기 위해 WebMvcConfigureAdapter 클래스를 상속받았다. 

.addResourceHandler : 호출 경로로 사용될 url 값 입력

.addResourceLocations : 실제로 파일이 위치할 폴더 경로

 

리소스 주소에 MD5 값 설정 

- 브라우저의 캐시는 요청한 request 파일명이 같으면 동작함 

- 브라우저의 캐시 제어와 상관 없이 애플리케이션에서 웹 리소스에 대한 캐시를 관리하고 싶으 경우 

-> 스프링에서 제공하는 콘텐츠 버전 정책 사용 

-> URL 주소에 해시값잉 추가, 캐시 주기도 별도로 설정 가능 

- 마찬가지로 addResourceHandlers를 통해 설정할 수 있다. 

@Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/assets/**")
                .addResourceLocations("classpath:/assets/", "/assets/")
                .setCachePeriod(3600)  //60 * 60 * 24 * 365 1year
                .resourceChain(true)
                .addResolver(new VersionResourceResolver().addContentVersionStrategy("/**"));
}

setCachePeriod : 캐시 만료일 설정 

resourceChain : true로 설정하면 VersionResourceResolver와 같은 추가 설정 가능

VersionReourceResourceResolver에 addContentVersionStrategy : 리소스 파일 로드시 해시 적용

 

웹페이지에 버전 리소스 리졸버 적용

versionResourceResolver를 통해 웹 리소스를 호출하려면 ResourceUrlProvider 를 이용하는 것이 좋다. 

@ControllerAdvice
public class ResourceAdvice {
    @Autowired
    private ResourceUrlProvider resourceUrlProvider;

    @ModelAttribute("versionResourceResolver")
    public ResourceUrlProvider versionResourceResolver() {
        return this.resourceUrlProvider;
    }
}

ModelAttribute 어노테이션을 통해 versionResourceResolver를 사용한다. 


3. 템플릿 엔진

서버에서 페이지를 만드는 경우 템플릿 엔진을 사용하는 것이 좋다. 

템플릿 엔진 : 서식(html)과 데이터(데이터베이스에 저장된 데이터)를 결합한 결과물을 만들어 주는 도구 

 

1) 설정

build.gradle에 다음 코드를 추가

 

compile "org.springframework.boot:spring-boot-starter-thymeleaf"

 

th.html에서 데이터를 출력할 수 있는 controller를 작성한다. 

@Controller
public class UIController {
    @Autowired
    InMemoryProductService inMemoryProductService;

    @RequestMapping(value = "/th")
    public String templatePage(Model model){
        model.addAttribute("message", "boot template");
        return "th";
    }
}

Model 객체에서 message의 값인 boot template을 출력할 것이다. 

 

2) 속성 

변수 표현식 : ${변수명} -> 변수 값 출력

객체의 속성을 출력하는 경우 : th:object = ${객체}로 객체이름을 명시, *{객체의 속성}을 통해 객체의 속성을 사용 가능

<div th:object="${product}">
    <p>name: <span th:text="*{name}"></span></p>
    <p>color: <span th:text="*{color}"></span></p>
    <p>price: <span th:text="*{price}"></span></p>
</div>

 

조건문 : if, unless, case 사용가능

    <div th:if="${product.price} > 3000" th:text="비싸다"/>
    <div th:if="${product.price} > 1500" th:text="적당하다"/>
    <div th:unless="${product.price} >3000" th:text="비싸다"/>

 

반복문 : each 태그를 이용하여 표현 가능 

<tr th:each="prod : ${products}">
        <td th:text="${prod.color}"></td>
        <td th:text="${prod.productName}"></td>
        <td th:text="${prod.price}"></td>
</tr>

4. WebJars를 이용한 프론트라이브러리 관리 

자바스크립트 라이브러리를 서버 라이브러리처럼 통합해서 사용가능 

 

1) WebJars 적용 

다음의 웹페이지에서 webJar로 사용할 수 있는 라이브러리 검색하여 사용 가능

http://www.webjars.org/ 

 

예를 들어 jquery와 부트스트랩을 추가하여 이용하는 경우 

build.gradle에 다음의 의존성을 추가한다. 

compile 'org.webjars:jquery:3.1.0'
compile 'org.webjars:bootstrap:3.3.1'

이제 html 페이지에서 다음의 코드로 사용할 수 있다. 

<script type="text/javascript" src="/webjars/jquery/3.1.0/jquery.min.js"></script>
<link rel="stylesheet" href="/webjars/bootstrap/3.3.1/css/bootstrap.min.css"/>
<script src="/webjars/bootstrap/3.3.1/js/bootstrap.min.js"></script>

 

localhost:8080을 입력했을 때 home.html이 호출되도록 WebConfig 클래스에서 addViewControllers 메서드를 오버라이드한다.

@Override
public void addViewControllers(ViewControllerRegistry registry) {
    registry.addViewController("/").setViewName("home");
}

 

 addViewControllers 메서드 : 페이지에 데이터를 전달하지 않고 페이지와 URL 연결만 필요한 경우 사용함

.addViewController : 매핑될 url 지정 

.setViewName : 페이지 이름을 지정

 

버전 정보를 축약해서 나타내는 경우 

bulid.gradle에 다음을 추가 

compile group: 'org.webjars', name: 'webjars-locator', version: '0.32'

html에서 다음과 같이 사용가능 

<script type="text/javascript" th:src="@{/webjars/jquery/jquery.min.js}"></script>

 

2) 인터셉터 활용

3장에서 컨트롤러의 실행 시간을 계산하는 ExecuteTimeInterceptor를 다음과 같이 구현 가능

public class ExecuteTimeInterceptor extends HandlerInterceptorAdapter {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        long startTime = System.currentTimeMillis();
        request.setAttribute("startTime", startTime);

        String reqUri = request.getRequestURI();
        System.out.println("reqUri: " + reqUri);

        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        long startTime = (Long) request.getAttribute("startTime");
        long endTime = System.currentTimeMillis();
        long executeTime = endTime - startTime;

        request.setAttribute("executeTime", executeTime);

        System.out.println("[" + handler + "] executeTime: " + executeTime + "ms");
    }
}

이렇게 만든 인터셉터를 WebConfig에 등록해야한다. 

 

@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(executeTimeInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns("/");
    }

    @Bean
    public ExecuteTimeInterceptor executeTimeInterceptor() {
        return new ExecuteTimeInterceptor();
    }
}

@Bean을 통해 ExecuteTimeInterceptor의 인스턴스를 스프링 컨테이너에 등록한다. 

addInterceptor  : executeTimeInterceptor를 추가하고 호출될 경로를 설정. 

addPathPatterns : 호출될 경로 설정 

excludePathPatterns : 파라미터를 "/"으로 설정 -> 인덱스 페이지가 호출될 경우 인터셉터가 호출되지 않음

'Spring > 스프링 부트로 배우는 자바 웹 개발' 카테고리의 다른 글

Chapter 1~3  (0) 2021.11.06

이번에 동아리에서 이 책을 이용하여 스터디를 진행하게 되었다. 

스터디를 진행하면서 책을 읽은 내용을 정리해보려고 한다. 

 

Chapter 1,2는 이미 간단하게 진행해서 추후에 내용을 다시 정리해서 작성하고, Chapter3 부터 정리한다. 

 

교재의 상세한 코드는 아래의 링크에서 확인 가능하다. 

https://github.com/thecodinglive/JPub-JavaWebService

 

GitHub - thecodinglive/JPub-JavaWebService: here is book example

here is book example. Contribute to thecodinglive/JPub-JavaWebService development by creating an account on GitHub.

github.com


Chapter 3 스프링 프레임워크 

1. 빈 + 컨테이너

스프링은 객체를 관리해주는 빈 컨테이너 프레임워크이다.

 

1) 웹 관리 컨데이너

- 웹 프로젝트에서 서버가 실행되는 동안 여러 가지 일을 개별적으로 처리하는 것은 힘듦. 

   -> 자바 빈을 대신 관리해주는 컨테이너를 찾음 

   -> EJB를 사용하게 됨 (웹 관리 컨테이너의 시작)

- 개발의 요구사항이 복잡해짐에 따라 경량화되고 간소화된 컨테어너 선호

   -> EJB가 필요한지 고민하게 됨

   -> POJO(일반 자바 클래스)를 사용하기 시작

 

2) 스프링

- 로드 존슨에 의해 개발됨

- 웹 애플리케이션 컨테이너와 상관없이 독립적으로 빈의 생명주기를 관리 가능

- Spring.IO에 스프링 기반으로 추가된 프로젝트들을 공개 중

- 다양한 모률로 확장 중 -> Spring Core Container를 통해 모듈 간의 의존도를 최소화했기 때문 

- 의존도를 낮추는 방법 중 하나가 IoC 패턴


2. IoC 패턴 활용 

- IoC(Inversion of Control) : 제어의 역전이라고 부름

- 프로그맴의 생명 주기에 대한 주도권이 웹 애플리케이션 컨테이너에 있기 때문에 IoC 패턴이 웹 개발에서 유용

- 웹 개발 시 데이터 베이스 처리, 파일 처리 인스턴스를 관리해야함

   -> 일괄적으로 인스턴스를 관리하기 위해 객체 생성을 관리하는 도구가 필요함 (DIP:의존 관계 역전 원칙)

 

의존관계 역천 원칙 DIP

  • high-level 모듈은 low-level 모듈에 의존하지 않고 인스턴스에 의존해야한다. 
  • 세부사항이 추상화에 의존해야한다. (그 반대는 안된다) -> 인터페이스 활용하여 결합도를 낮춘다.

의존성 주입 DI 

- 인터페이스를 사용하더라도 인스턴스화를 위해서는 객체 생성에 필요한 코드가 수반됨

   -> 결합도를 완전히 분리할 수 없음 

   -> 이 문제를 해결하는 것이 DI

- 대신 의존성을 주입해준다. 

 

1) 인터페이스와 스프링

인터페이스를 활용한 예제

Boss와 Employee라는 2개의 클래스는 WorkManager 인터페이스를 상속 받아 doIt 메서드를 가지고 있다. 

public class Boss implements WorkManager{
	@Override
    public String doIt(){
    	return "do boss";
    }
}
public class Employee implements WorkManager{
	@Override
    public String doIt(){
    	return "do work";
    }
}

 

Boss와 Employee 클래스가 일하는 WorkerService를 만든다. 

@Setter
public class WorkService{
    // 참고로 이런식으로 인스턴스 변수를 가지는 것을 디자인 패턴에서 strategy pattern이라고 한다.
	WorkManager workManager; 
    
    public void askWork(){
    	System.out.println(workManager.doIt());
    }
}
public class BasicApp{
	public static void main(String ar[]){
    	WorkService workService = new WorkService();
        WorkManager employee = new Employee();
        WorkMagaer boss = new Boss();
        
        workService.setWorkManager(employee);
        workService.askWork();
        
        workService.setWorkManager(boss);
        workService.askWork();
    }
}

실제로 실행해보면 workService의 askWork의 출력결과가 setter에 따라 다르다는 것을 확인할 수 있다. 

 

2) 스프링 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.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="boss" class="basic.Boss" init-method="onCreated" destroy-method="onDestroyed"></bean>
    <bean id="employee" class="basic.Employee" init-method="onCreated" destroy-method="onDestroyed"></bean>

    <bean id="myWorkService" class="basic.WorkService">
        <property name="workManager">
            <ref bean="boss"/>
        </property>
    </bean>

    <bean id="yourWorkService" class="basic.WorkService">
        <property name="workManager">
            <ref bean="employee"/>
        </property>
    </bean>

</beans>

 

중요한 것은 스프링 applicationContext.xml에 bean 태그를 통해 boss, employee, myWorkService, yourWorkService를 빈으로 등록할 수 있다. 빈으로 등록한 객체에 대해서는 스프링이 대신 인스턴스를 생성하기 때문에 new 연산자를 통해 인스턴스를 생성하지 않아도 된다. 

 

public class BasicApp{
	public static void main(String ar[]){
    	GenericXmlApplicationContext context = new GenericXmlApplicationContex("classpath:applicationContext.xml");
        
    	WorkService yourWorkService = context.getBean("yourWorkService", WorkService.class); // xml 상에서 구현체를 employ로 명시함
        yourWorkService.askWork();
        
    	WorkService WorkService = context.getBean("myWorkService", WorkService.class); // xml 상에서 구현체를 boss로 명시함
        WorkService.askWork();
    }
}

개별적으로 new를 사용하여 객체를 생성하지 않고 스프링에서 bean으로 등록한 다음 참조하여 사용한다. 

 

XML 설정시 빈 생명주기 제어

스프링에서 객체를 빈으로 등록하거나 제거할 때 콜백 메서드를 등록할 수 있다. 

  • init-method : 스프링으로 클래스가 최기화되면 호출될 메서드를 추가한다. 설정할 클래스에 onCreated 메서드를 추가하고, xml파일에서 init-method="onCreated"를 추가한다. 
  • destory-method : 클래스 소멸시에 호출되는 메서드를 추가한다. 설정할 클래스에 onDestroyed 메서드를 추가하고, xml 파일에서 destroy-method="onDestroyed"를 추가한다. 

 

3) 스프링 JavaConfig 설정

@Configuration 어노테이션을 클래스 상단에 추가하고, @Bean 태그를 통해 매서드를 추가할 수 있다. 

@Configuration
@Import(CompanyConfig.class)
public class BeanConfig {
    @Bean
    public WorkManager employee() {
        return new Employee();
    }

    @Bean
    public WorkManager boss() {
        return new Boss();
    }

    @Bean
    public WorkService yourWorkService() {
        WorkService workService = new WorkService();
        workService.setWorkManager(employee());
        return workService;
    }

    @Bean
    public WorkService myWorkService() {
        WorkService workService = new WorkService();
        workService.setWorkManager(boss());
        return workService;
    }
}
public class JavaConfigSpringApp {
    public static void main(String ar[]){
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(BeanConfig.class);
        context.refresh();

        WorkService yourWorkService = context.getBean("yourWorkService", WorkService.class);
        yourWorkService.askWork();

        WorkService myWorkService = context.getBean("myWorkService", WorkService.class);
        myWorkService.askWork();

        context.close();
    }
}

 

AnnotationConfigApplicationContext의 인스턴스를 사용하여 빈을 로드하는 것 외에 다른 부분은 동일하다. 

 

@Import

@Import 어노테이션을 사용하여 설정 내용을 파일별로 분리하여 사용할 수 있다. 

예를 들어 CompanyConfig.java도 bean 설정 파일로 사용하는 경우,

@Import(CompanyConfig.class)를 BeanConfig 클래스 상단에 추가하면 된다. 

 

어노테이션 설정 시 생명주기 제어

  • @PostConstruct : 빈이 초기화될 때 호출된다. 추가할 클래스의 메서드 상단에 어노테이션을 추가한다. 
  • @PreDestroy : 종료될 때 호출된다. 마찬가지로 메서드 상단에 어노테이션을 추가하여 설정한다. 

3. Spring MVC

1) 스프링 MVC 구조 

스프링 MVC 구조는 DispatcherServlet, View Resolver, Interceptor, Handler, View 등으로 구성됨

 

사진 출처 - [Spring] Spring의 MVC 패턴과 MVC1과 MVC2 비교 ChanBLOG (chanhuiseok.github.io)

 

전체적인 흐름

DispatcherServlet이 요청을 받으면 그 요청를 처리할 수 있는 Handler의 이름을 HandlerMapping에 물어봄 

-> HandlerMapping은 요청 URL을 보고 Handler를 판단하고 Handler 실행 전에 전처리, 후처리로 실행할 인터셉터 목록을 정함 

-> DispatcherServlet은 제어권을 Handler로 전달

-> Handler는 응답에 필요한 서비스 호출, 렌더링할 ViewName을 ViewResolver에 전달

-> ViewResoler는 응답에 필요한 View 생성

-> VIiew에 Model과 컨트롤러를 전달하여 응답 생성 

-> 응답을 클라이언트에 반환

 

스프링 MVC에서는 DispaterServlet을 web.xml에 등록하고 요청받은 루트 url를 매핑하는 것만으로 해당 역할 대체 가능 

 

2) 스프링 MVC 설정 및 DispatcherServlet 설정 

(자세한 코드는 분석하지 않고 넘어간다. )

  1. web.xml에 DispatcherServlet을 설정한다. 
  2. dispatcher-servlet.xml 파일에 인터셉터, 컨트롤러 설정을 bean으로 지정 
  3. xml 스키마 정의 파일에 대한 정보도 지정함.
  4. view이름을 처리할 viewResolver를 bean으로 등록함 

 

3) 컨트롤러와 뷰

@Controller
public class IndexController {

	@RequestMapping("/")
    public ModelAndView home(){
       // return new ModelAndView("home");
        ModelAndView mv=new ModelAndView("home");
        mv.addObject("title", "Jpub Spring WEB");
        mv.addObject("today", new Date().toString());

        return mv;
    }
}

어노테이션을 통해 Controller로 설정한다. 

@RequestMapping을 통해 controller가 실행될 url을 정의할 수 있다. 

예를 들어 @RequestMapping("home")은 localhost/home에 접속할 경우 호출된다.

이 예제에서는 루트 즉 localhost 접속시 해당 컨트롤러가 호출 된다. 

 

ModelAndView를 통해 데이터에 해당하는 모델과 view를 지정할 수 있다. 

이 예제에서는 호출할 뷰 이름을 home으로 지정하여 localhost에 접속할 경우 home.jsp의 뷰가 출력될 것이다. 

 

addObject를 통해 모델의 이름과 데이터를 뷰에 전달할 수 있다. 

map과 유사하게 (key, value) 형태로 사용할 수 있다. 

이 예제의 경우 view에서 title 모델을 사용하면 Jpub Spring WEB이 출력되고, 

today 모델을 사용하면 today의 값인 현재 시간이 출력된다. 

즉 view에서 key 값을 통해 value를 사용할 수 있다. 

 

4) 인터셉터 

- 인터 셉터를 이용하여 컨트롤러가 요청을 처리하기 전과 후의 로직을 추가할 수 있다. 

- dispatcher-servlet.xml에 인터셉터 태그를 추가하여 설정할 수 있다. 

- mapping 태그에 요청받을 경로를 속정으로 정의할 수 있다. 

'Spring > 스프링 부트로 배우는 자바 웹 개발' 카테고리의 다른 글

Chapter 4  (0) 2021.11.07

※유료 강의이므로 자세한 내용을 적지 않습니다. 자세한 내용이 궁금하시다면 강의를 수강해보세요

 

강의 링크

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/dashboard

 

스프링 핵심 원리 - 기본편 - 인프런 | 강의

스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., 스프링 핵심 원리를 이해하고, 성장하는 개발자가 되어보세요! 📣 확인해주

www.inflearn.com

 

스프링의 탄생

자바 진영의 정파 기술 EJB(Enterprise Java Beans)

  • Spring 과 jpa를 모두 섞어 놓은 것
  • 자바의 표준 기술 금융권에서 많이 사용
  • EJB로 기술 영업 많이 함, 보급이 잘 되었음
  • Container, 트랜젝션, 분산 기술 지원이 잘 되었음.
  • ORM도 지원 (ORM: 자바객체를 DB에 넣고 뺄 수 있음(쿼리 사용X))
  • 비용이 비쌈
  • 개발이 힘듦..(지옥)

 

스프링

-      EJB 컨테이너 대체

-      단순함의 승리

 

 

하이버네이트

-      EJB 엔티티빈 사용

-      JPA 새로운 표준 정의

 

자바 새로운 정의 -> 하이버네이트를 통해 JPA를 만듦

 

 

스프링의 역사

-      2002년 로드 존슨 책 출간

-      EJB의 문제점을 지적하여 EJB 없이도 충분히 고품질의 애플리케이션을 개발할 수 있음을 보여줌

-      유겐 휠러, 얀 카로프, 로드 존슨이 오픈소스 프로젝트를 시작함

-      스프링의 핵심 코드의 상당수는 유겐 휠러가 지금도 개발

 

 

스프링 기능

스프링 프레임워크

  • 가장 중요함
  • 핵심 기술: 스프링 DI 컨테이너, AOP, 이벤트, 기타
  • 웹 기술: 스프링 MVC, 스프링 WebFlux
  • 데이터 접근 기술: 트랜잭션, JDBC, ORM 지원, XML 지원
  • 기술 통합: 캐시, 이메일, 원격 접근, 스케줄링
  • 테스트: 스프링 기반 테스트 지원
  • 언어: 코틀린, 그루비
  • 스프링부트를 통해 스프링 프레임 워크의 기술을 편리하게 사용가능

(강의에서는 핵심 기술에 초점을 맞추어 진행)

 

 

스프링 부트

  • 스프링을 편리하게 사용할 수 있도록 지원, 최근에는 기본으로 사용
  • 단독으로 실행할 수 있는 스프링 애플리케이션을 쉽게 생성 (Tomcat 같은 웹 서버를 내장해서 별도의 웹 서버를 설치하지 않아도 됨)
  • 지금은 빌드부터 서버 띄우는 것까지 해준다. (톰캣 설치 필요 없음)
  • 손쉬운 빌드 구성을 위한 starter 종속성 제공
  • 편하게 라이브러리 구성 가능
  • 스프링과 3rd path 라이브러리 자동 구성
  • 예전에는 버전에 따라 라이브러리가 서로 호환이 안되는 경우가 있었음
  • 지금은 호환되는 버전을 자동으로 구성해준다.
  • 메트릭, 상태 확인, 외부 구성 같은 프로덕션 준비 기능 제공
  • 운영에서 모니터링하기 편함
  • 관례에 의한 간결한 설정
  • 대부분 관례에 의해 설정하고, 필요한 부분만 커스텀해서 사용한다.

 

그외 스프링 기능

  • 스프링 데이터: CRUD를 쉽게 할 수 있도록 도와줌
  • 스프링 세션: 세션을 쉽게 사용가능
  • 스프링 시큐리티: 보완 관련
  • 스프링 Rest Docs: Api 문서를 편리하게
  • 스프링 배치: 예를 들어 천만 건의 데이터를 업데이트 및 저장함
  • 스프링 클라우드: 클라우드 관련 기술

그외 기능이 많다.

 

 

스프링 단어 정의

  • 문백에 따라 다르게 사용

-      스프링 DI 컨테이너 기술

-      스프링 프레임워크

-      스프링 부트, 스프링 프레임워크 등을 모두 포함한 스프링 생태계

 

 

스프링은 왜 만들었는가?

-      스프링은 자바 언어 기반의 프레임워크

-      즉 객체 지향 언어의 강력한 특징을 살려내는 프레임워크

-      스프링은 좋은 객체 지향 애플리케이션을 개발할 수 있는 프레임워크

 

 

 

객체 지향 프로그래밍

  • 객체 지향 프로그래밍은 컴퓨터 프로그램을 별개의 독립된 단위, 즉 “객체”들의 모임으로 파악하고자 하는 것이다. 각각의 객체는 메시지를 주고받고 데이터를 처리할 수 있다.(협력)
  • 객체 지향 프로그래밍은 프로그램을 유연하고 변경이 용이하게 만들 수 있음
  • ->레고 블록 조립하듯, 부품을 갈아 끼우듯이 컴포넌트를 쉽고 유연하게 변경하면서 개발할 수 있는 방법(다형성)

 

다형성이란?

-      실세계와 객체 지향을 1:1로 매칭할 수 없으나 그래도 비유하면 이해하기 쉽다.

-      역할(인터페이스)과 구현(실제 구현)으로 세상을 구분

-      구현이 바뀌어도 역할은 변하지 않는다!

 

 

예시

  • 자동차를 바뀐다고 해도 운전자의 역할은 바뀌지 않는다.
  • 새로운 자격증 필요 없음, 자동차 운전 가능
  • 운전자는 자동차의 역할에 의존하고 있다.
  • 운전자는 자동차의 내부 구조를 알 필요 없음
  • 자동차가 바뀌어도 운전자에게 영향을 주지 않음
  • 운전자를 바꿀 필요 없음
  • 클라이언트에 영향을 주지 않고 새로운 기능 제공 가능
  • 새로운 자동차가 나와도 클라이언트를 바꿀 필요 없다.

 

 

역할과 구현을 분리

클라이언트는 내부구조를 몰라도 되며, 구현 대상의 구조가 변경되어도 영향을 받지 않음.

클라이언트는 구현 대상 자체를 변경해도 영향 받지 않는다.

 

 

프로그램에서 역할과 구현 분리

 

다형성

  • 역할= 인터페이스, 구현=인터페이스를 구현한 클래스, 구현 객체
  • 핵심은 구현보다 인터페이스가 먼저이다.
  • 객체의 협력이라는 관계부터 생각한다.
  • 혼자 있는 객체는 없다.
  • 클라이언트: 요청, 서버: 응답
  • 수많은 객체 클라이언트와 객체 서버는 서로 협력 관계를 가짐
  • 응답=클라이언트의 요청을 해결하는 것(void도 해당된다. 요청을 처리하기만 하면 된다)

 

Overriding

-      결과적으로 overriding된 메서드가 실행된다.

-      다형성으로 인터페이스를 구현한 객체를 실행 시점에 유연하게 변경 가능

 

클라이언트는 MemberRepository(인터페이스)에 의존하고 있음

memberRepository를 바꾸어 구현해도 인터페이스에 할당할 수 있음

 

다형성의 본질

  • 인터페이스를 구현한 객체 인스턴스를 실행 시점에 유연하게 변경 가능
  • 다형성의 본질을 이해하려면 협력이라는 객체사이의 관계에서 시작해야 함
  • 클라이언트를 변경하지 않고, 서버의 구현 기능을 유연하게 변경 가능

 

정리

-      실세계의 역할과 구현이라는 컨셉을 다형성으로 구현가능

-      유연하고, 변경 용이

-      확장 가능한 설계

-      클라이언트에 영향을 주지 않는 변경 가능

-      인터페이스를 안정적으로 설계하는 것이 가장 중요함

 

 

한계

-      역할(인터페이스) 자체가 변하면 클라이언트, 서버 모두에 큰 변경이 발생함

 

 

 

스프링과 객체 지향

-      다형성이 가장 중요함

-      스프링은 다형성을 극대화하여 이용할 수 있도록 도와줌

-      IoC, DI는 다형성을 활용해서 역할과 구현을 편리하게 다룰 수 있도록 지원함.

-      구현을 편리하게 변경 가능

 

 

좋은 객체 지향 설계의 5가지 원칙(SOLID)(면접 빈출)

1.     SRP(Single responsibility principle): 단일 책임 원칙

  • 한 클래스는 하나의 책임만 가져야함
  •  한 책임이라는 것이 모호할 수 있음
    • 클 수도 있고, 작을 수도 있음
    • 문맥과 상황에 따라 다름
  • 중요한 기준은 변경!
    • 변경이 있을 때 파급 효과가 적으면 SRP를 잘 지킨 것
    • Ex) UI 변경, 객체의 생성과 사용을 분리
  • 범위를 적절하게 조절하는 것이 중요

 

2.     OCP: 개방-폐쇄 원식

  •  소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 함
  • 다형성을 활용
    • Ex. JDBCMemberRepository로 바꾸면 memberService의 코드를 변경해야함
    • MemberRepository m = new MemoryMemberRepository()
    •  MemberRepository m = new JdbcMemberRepository()로 변경 필요
    • 구현 객체를 변경하려면 클라이언트 코드를 변경해야 함(OCP가 깨짐)
  • 결론: 객체를 생성하고, 연관관계를 맺어주는 별도의 조립, 설정자가 필요

 

 

3.     LSP: 리스코프 치환 원칙

  • 프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타임의 인스턴스로 변경할 수 있어야 함
    • Ex. 자동차 인터페이스가 있고 구현체가 있을 때, 구현체가 악셀을 구현함.
    • 악셀을 밟아서 뒤로 가는 기능을 만들 수도 있음.
    • 그래도 컴파일 오류는 나지 않음 but 인터페이스 규약을 지키지 않음
    •  LSP 원칙 위배
  • 다형성에서 하위 클래스는 인터페이스 규약을 지켜야함. 다형성을 지원하기 위한 원칙
  • 인터페이스를 구현한 구현체는 믿고 사용하려면 이 원칙이 필요함

 

4.     ISP(Interface segregation principle) 인터페이스 분리 원칙

  • 특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나 보다 낫다
  • 자동차 인터페이스 -> 운전 인터페이스, 정비 인터페이스로 분리
  • 사용자 클라이언트 -> 운전자 클라이언트, 정비사 클라이언트로 분리 가능
  • 분리하면 정비 인터페이스 자체가 변해도 운전자 클라이언트에 영향을 주지 않음
  • 인터페이스가 명확해지고, 대체 가능성이 높아짐

 

5.     DIP(Dependency inversion principle) 의존 관계 역전 원칙

  •  프로그래머는 “추상화에 의존해야지, 구체화에 의존하면 안된다.”
    • 의존성 주입
    • 클라이언트 코드가 구현 클래스에 의존하지 않고, 인터페이스에 의존해야한다.
    • 역할에 의존해야 한다는 의미 
  • 구현체가 인터페이스에 의존해야 유연하게 구현체를 변경 가능! 구현체에 의존하게 되면 변경이 아주 어려워진다.
    • MemberService는 인터페이스에 의존하지만, 구현 클래스에 동시에 의존함.
    • MemberService 클라이언트가 구현 클래스를 직접 선택함(의존하고 있음)
    • MemberRepository m = new MemoryMemberRepository()
      • DIP 위반

 

정리

-       객체 지향의 핵심은 다형성

-       다형성만으로는 5원칙을 만족할 수 없음

 

 

스프링에서 객체 지향 이야기가 나오는 이유?

스프링은 다형성+OCP, DIP가 가능하게 지원

  • DI : 의존 관계, 의존성 주입
  • DI 컨테이너: 자바 객체를 컨테이너에 넣고 의존관계 주입
  • 클라이언트의 코드 변경 없이 기능 확장
  • 쉽게 부품 교체하듯 개발 가능

 

 

스프링이 없던 시절

OCP, DIP원칙을 지키면서 개발을 하면 할일이 너무 많음 -> 프레임워크를 만듦

순수 자바로 OCP, DIP 원칙을 지키면서 개발을 하면 결국 스프링 프레임워크를 만들게 됨(DI 컼테이너)

코드로 직접 짜보면 필요성을 알게 됨

 

 

정리

  • 모든 설계에 역할과 구현을 분리하자
  • 구현은 언제든지 유연하게 변경할 수 있는 수 있는 것이 좋은 객체 지향 설계이다
  • 이상적으로는 모든 설계에 인터페이스 부여
    • 어떤 데이터베이스를 사용할지 정해지지 않은 상태에서 개발을 하는 경우에도 인터페이스로 개발하면, 나중에 구현 기술 선택 가능
    • 할인 정책이 정확이 정해지지 않은 경우에도 구현 가능(기능 확장 가능)
  • 구현 기술이 바뀌더라도 전체 코드를 변경할 필요가 없다.

 

 

실무적인 고민

  • 무분별하게 인터페이스 도입 시 추상화 비용 발생
    • 개발자가 코드를 한 번 더 열어봐야 함(구현 클래스를 바로 볼 수 없음)
    • 장점이 단점을 넘어설 때 사용해라
  • 기능 확장할 가능성이 없다면 구현 클래스 직접 사용 -> 필요 시 리팩토링해서 인터페이스 도입

웹개발은 크게 정적 컨텐츠. MVC와 템플릿 엔진, API로 분류 가능 

 

정적 컨텐츠 

그냥 서버에서 하는 일없이 파일을 웹브라우저에 내려주는 것 

 

MVC와 템플릿 엔진

-제일 많이 하는 방법

-JSP, PHP -> 템플릿 엔진

: 서버에서 프로그래밍해서 html을 동적으로 바꿔서 내림 

-Controller, Model, View(템플릿 엔진 화면)의 3가지

-정적 컨텐츠는 그냥 파일을 그대로 고객에게 전달하는 것

-MVC와 템플릿 엔진은 서버에서 변형을 해서 내려주는 방식

 

API

-만약 안드로이드, ios 클라이언트와 개발시 json 등을 이용해서 클라이언트에게 데이터 전달

ex) view, react 

-데이터만 내려주면 화면은 클라이언트가 그림 

-서버끼리 통신시 사용

 

정적 컨텐츠 

resources/static 폴더에 html 파일 추가 

폴더의 파일을 그대로 서버에 내려준다. 

localhost:8080/파일이름.html로 접근 가능 

어떤 프로그래밍을 할 수 없다. 

 

MVC와 템플릿 엔진

MVC: Model, View, Controller

 

Controller와 View를 분리하여 코딩함 (관심사 분리)

Controller와 VIew 작성 후 실행시 parameter가 없으면 에러가 발생한다. 

required=true이면 값을 무조건 넘겨야 한다(default: true)

parameter를 넘기는 방법 

: localhost:8080/템플릿이름?변수=parameter

model에 변수:parameter로 넘긴다. 

 

API

responsebody annotation이 있으면 http 응답에 그대로 넘긴다. 

단순 문자면 StringHttpMessageConverter가 동작하여 문자 그대로 반환한다. 

객체이면 MappingJackson2HttpMessageConverter가 동작하여 json 형식으로 변환하여 http 응답에 반환한다. 

(xml 등 특정 포맷으로 변환 가능)

'Spring > 스프링 입문' 카테고리의 다른 글

Section 1  (0) 2021.05.15

+ Recent posts