본문 바로가기
Back-end

Spring Concept

by 신재권 2021. 8. 9.

스프링부트의 특징

  • 실행 가능한 단독 애플리케이션을 만들 수 있다.
  • 외부 와스 없이 내장된 톰켓, 제티 또는 언더토우 서버를 사용할 수 있다.
  • 라이브러리 관리를 위한 스프링 부트 스타터를 제공한다.
  • 스프링 라이브러리와 서드 파티 라이버르리를 위한 자동설정을 지원한다.
  • Xml 설정을 사용하지 않는다.

의존 주입의 개념(Dependency Injection)

의존 주입은 스프링 뿐만 아니라, 자바 객체 프로그래밍에서 매우 중요하게 생각하는 개념이다. 객체지향 프로그래밍에서 다른 객체를 사용하는 것을 다르게 표현한다면 '다른 객체에 의존한다'라고 표현한다.

  • A객체가 B,C 객체를 이용한다.
  • A객체는 B,C 객체의 기능에 의존한다.

다른 객체를 사용하기 위해서는 사용하려는 A객체가 B, C객체를 직접 생성하여 사용할 수도 있고, 미리 만들어져 있는 것을 할당받아서 사용할 수도 있다.

  • A객체가 B, C 객체를 직접 생성한다.
  • B, C 객체가 외부에 생성되어 있고, A객체에 주입된다.

오른쪽 그림은 미리 만들어져 있는 객체인 B, C의 기능이 A에게 필요할 때 A의 세터나 생성자를 통해 A에게 B, C의 객체를 주입시켜 기능을 사용할 수 있게 만드는 구조이다. 이때 사용되는 용어가 의존 주입인 것이다.

여기서 B, C 객체를 생성하고 B, C 객체의 라이프 사이클을 관리하고 A 객체에 의존 주입을 관리해주는 무엇인가를 컨테이너라고 부른다.

개발자가 직접 객체를 제어하지 않고 컨테이너로 객체의 제어권이 넘어갔다는 의미로 제어의 역전(Inversion Of Control : IoC) 이라는 용어가 사용된다.

 

스프링이 이 IoC 컨테이너의 역할을 한다. 스프링은 객체를 생성, 라이프 사이클 관리 및 필요로 하는 객체에 의존 주입을 하는 컨테이너이고 ,라이브러리들의 집합체라고 할 수 있다.

강한 결합 vs 약한 결합

객체 간의 의존 관계에서 직접 객체를 생성하면 생성부터 메모리 관리를 위한 소멸까지 객체의 라이프사이클을 개발자가 다 관리해야 하므로 강한 결합이 된다.

그리고 이미 누군가 생성한 객체를 주입받을 경우, 사용하기만 하면 되므로 약한 결합이 된다. 객체 지향 프로그래밍에서 약한 결합(느슨한 결합)을 사용하게 되면 개발자가 관리할 것이 적어진다는 장점이 있다.

자바의 클래스로 예시를 들면

class Member{
	String name;
	String nickname;
	public Member() {}
}
public class UnderstandDI{
		public static void main(String[] args){
		
}

public static void memberUse1(){
		//강한 결합 : 직접 생성
	Member m1 = new Member();
}

public static void memberUse2(Member m){
//약한 결합 : 생성된 것을 주입 받음- 의존 주입 (Dependency Injection)
	Member m2 = m;
	}
}

생성자를 직접 호출해서 객체를 만들게 되면 강한 결합이 된다. 이에 비해 약한 결합은 의존할 객체를 주입받야아 하므로, 메서드의 파라미터로 받아들여 객체에 할당하는 코드를 작성하면 된다.

이런 강한 결합, 약한 결합의 대상은 개발자가 만든 클래스 뿐만 아니라, Date와 같이 기존에 만들어져 있는 API나 프레임워크의 기능에도 똑같이 적용된다.

 

왜 약한 결합이 프로그래밍을 할때 유리한지 예를 만들어서 살펴보자.

전체가 static 메서드로 이루어져 있는 유틸 클래스들은 객체를 인스턴스화할 필요가 없어 흔히 클래스를 만들 때 인스턴스화를 막기위해 생성자를 private으로 만든다.

 

만약 위의 예제에서 Member클래스가 유틸 클래스는 아니지만 테스트를 위해 생성자의 public 부분을 private으로 변경하면 강한 결합의 경우 에러가 발생한다. 하지만 약한 결합의 경우 에러가 발생하지 않는다.

이처럼 약한 결합을 사용하는 프로그래밍은 다른 클래스의 변화에 더욱 안전하고 유연하게 대처할 수 있는 프로그래밍이 될 수 있다.

 

의존 주입을 통해 약한 결합을 사용하는 이유이다.

실제 스프링 프로젝트에서는 스프링 컨테이너가 웹에서 사용할 수 있는 공통 기능에 대한 많은 클래스를 미리 만들어두고, 우리가 필요한 곳에 의존 주입을 해주는 역할을 하므로 많은 라이브러리들이 안전하게 이용해서 웹 애플리케이션 개발을 손쉽게 할 수 있도록 도와준다.

자바 코드로 DI 사용하기

스프링에서는 다음과 같이 다양한 방법으로 의존 주입이 가능하다.

  • 빈 설정 xml을 통한 의존 주입
  • 자바 코드를 이용한 의존 주입
  • 어노테이션을 이용한 의존 주입

스프링에서는 보통의 경우 xml을 이용한 의존 주입이 많이 이용된다. 그래서 프로젝트의 규모가 커질수록 빈 설정을 위한 xml 설정도 과도하게 많아진다. 이에 반해 스프링 부트는 xml의 과도한 설정을 없애기 위해서 xml을 이용한 의존 주입을 사용하지 않고 다음의 두 가지 의존 주입을 많이 사용한다.

  • 자바 코드를 이용한 의존 주입(필요한 경우에만)
  • 어노테이션을 이용한 의존 주입

스프링 부트가 실행되면 스프링 부트는 IoC 컨테이너가 되어 오토 스캔에 의하여 어노테이션이 지정된 클래스들을 찾아 빈으로 등록한다.

 

@Component 어노테이션에 빈의 이름을 추가 정보로 입력한 것들은 입력한 이름이 빈의 이름으로 사용되고, 빈의 이름을 입력하지 않고 @Component 어노테이션만 지정하면 클래스의 첫 글자를 소문자로 한 클래스의 이름이 빈의 이름으로 등록이 된다.

스프링 부트로 만든 웹 프로젝트는 웹 브라우저에서 주소창에 URL을 입력하면 RequestMapping에 등록된 메서드가 호출되는 방식이다.

빈의 메서드를 호출하기 위해서도 이와 같은방식을 사용해야 한다.

어노테이션

@가 붙어있는 명령어로

컴파일러에게 정보를 알려주거나 추가적인 지시를 하는 지시어로, 어노테이션의 종류는 다양하다.

@SpringBootApplication : 스프링부트는 이 어노테이션에 의해 자동으로 설정이 이루어지고 컴포넌트가 등록이 된다.

@SpringBootApplication의 3가지 기능

  1. @Configuration : Bean을 생성할 때 Singletone으로 한 번만 생성한다. 그리고 각종 설정을 세팅한다.
  2. @EnableAutoConfiguration : 스프링 어플리케이션 컨텍스트(Application Context)를 만들 때 자동으로 설정하는 기능을 켠다. 사용자가 필요할 것 같은 빈을 추측해서 ApplicationContext를 만들 때 필요한 설정을 한다. 클래스 패스를 기준으로 클래스를 찾아 설정을 한다. 예를 들어 클래스 패스에 tomcat-embeded.jar가 있으면 TomcatEmbeddedServletContainerFactory가 있을 것이라고 추측해서 설정한다.
  3. @ComponentScan : 지정한 위치 이하에 있는 @Component와 @Configuration이 붙은 클래스를 스캔해서 빈으로 등록한다.

@Configuration : 스프링 설정으로 사용됨을 의미한다.


@Bean : 메서드의 리턴값은 빈 객체로 사용됨을 의미한다.

Bean 어노테이션의 이름을 주면 해당 클래스를 빈으로 등록할 떄 입력한 이름으로 빈의 이름을 변경할 수 있다.


@Value : 세터의 역할을 수행하기에, 객체가 생성될 때 값을 가지고 만들 수 있도록 값의 지정이 가능하다.

또한 생성된 후 값을 바꿀 일이 없으면 하단 세터 부분의 코드는 필요 없지만, 프로그램 수행 중에 내용이 바뀌어야 한다면 하단의 세터 부분을 이용해 값을 바꿀 수 있다.


@Autowired : 해당 클래스의 객체를 찾아와 자동으로 연결된다. 컨테이너에 등록된 빈들 중에서 사용할 수 있는 객체를 찾아서 자동으로 연결해 주는데, printerA, printerB처럼 유사한 클래스가 여러 개 있는 경우 @Qualifier 어노테이션을 통해 이름을 명확하게 지정해줘야 한다.


정적 리소스 사용하기

정적인 요소 : js, css, image 등

스프링부트의 뷰 리졸버(View Resolver)는 클라이언트가 원하는 컨텐츠 타입을 고려하여 뷰를 결정하는데 다음과 같이 여러가지 뷰 형식을 동적인 문서로 만들 때 사용할 수 있다.

원하는 뷰 타입의 디펜던시를 프로젝트 생성시 추가하면 동적인 문서로 사용할 수 있다.

  1. FreeMarker
  2. Groovy
  3. Thymeleaf
    1. 프로젝트 생성 시 디펜던시를 추가했다면 추가적인 설정 없이 템플릿 폴더 아래 확장자 html파일을 뷰로 만들어 사용할 수 있다.
    2. html 파일의 내용은 html과 거의 유사하지만 마치 jsp처럼 동작한다. 이때 부터 html 파일은 정적인 파일이 아니고 동적으로 컨텐츠를 표현하는 파일이 된다.
  4. Velocity
  5. JSP
    1. 스프링 부트에서 기본으로 지원되지 않기에 제공되는 스타터가 없다.
    2. 프로젝트를 생성한 후에 추가적인 설정을 해야 사용 가능하다.
    3. 국내에서는 거의 표준이므로 추가적인 설정을 하고 사용한다.

Spring Web 디펜던시

스프링부트는 다양한 스터드들을 제공하는데 'spring-boot-start-모듈명' 형태의 이름을 갖는 파일들이 바로 이런 스타터이다. 먼저 "Spring Web"은 스프링 MVC를 사용하는 RESTful 애플리케이션을 포함한 웹 구축을 위한 스타더이며, 톰캣을 기본 내장 컨테이너라고 사용한다.

이렇게 Spring Web 스타터를 지정하면 웹 기능을 사용하기 위한 다양한 의존성들을 프로젝트 생성시 자동으로 추가해준다.

  • 스프링부트에서 "정적인 요소는 resources 하위의 static 폴더에 저장하고 사용하면 된다"이다.

정적 리소스 사용하기

스프링 부트에서 JSP를 사용하기 위해서는 추가적인 설정이 필요하다.

기본적으로 JSP의 사용에 다음과 같은 제약이 있다.

첫번째로 실행 가능한 jar 파일이 만들었을 때 JSP가 동작하지 않는다고 쓰여있다. 다만 우리가 프로젝트 생성시 선택한 war 타입은 실행 가능한 war 파일로 만들었을 때 내상 와스(WAS)로 실행하거나 외부 와스에 배포를 하더라도 JSP가 정상적으로 동작하므로 문제 없다.

두번째로 내장 서버로 제티(Jetty)를 사용할 수 없다는 점이다.

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

//jstl을 사용하기 위한 라이브러리 추가
implementation 'javax.servlet:jstl'
//톰캣이 jsp 파일을 컴파일 할 수 있도록 만들어주는 라이브러리를 추가
implementation 'org.apache.tomcat.embed:tomcat-embed-jasper'

이 명령을 통해 build.gradle에 변경된 내용대로 다운로드가 되지 않은 라이브러리가 있다면 지정한 저장소에서 사용가능한 버전이나 지정된 버전으로 변경된 라이브러리가 다운된다. 그러므로 변경된 내용이 있으면 반드시 이 명령을 수행해야 한다.

  • 프로젝트 생성시 추가하지 못하였거나, 프로젝트 개발 중간에 더 필요한 디펜던시가 생기면 이와 같은 방법으로 build.gradle에 디펜던시를 언제든지 추가 가능하다.

스프링 부트에서 기본으로 제공하는 다른 템플릿 뷰들과 달리 JSP 는 src/main/resources의 템플릿 폴더를 사용할 수 없다.

추가한 JSP 파일의 내용을 작성하기 전에 JSP 사용이 가능하도록 설정을 마무리 하여야 한다.

application.properties 을 클릭하여 다음 내용을 추가한다.

스프링 부트 애플리케이션은 프로그램을 시작할 떄 application.properties 파일에 정의된 내용을 로드한다.

#JSP 설정
spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.jsp

spring.mvc.view.prefix=/WEB-INF/views/ : 접두어

spring.mvc.view.suffix=.jsp : 접미어

결과 = /WEB-INF/views/test1.jsp 호출

 

리퀘스트 매핑을 통해서 사용자가 특정 url을 호출하면 JSP 파일들이 호출되게 할 수 있다.

 

@ResponseBody 어노테이션이 메서드에 적용되어 있으면 리턴되는 스트링 값 자체만 웹 브라우저로 전달된다. @ResponseBody 어노테이션이 없다면 조합된 값으로 JSP 를 찾아 실행하고 그 결과를 웹 브라우저에게 전달한다.

 

컨트롤러는 @RequestMapping 어노테이션이 적용된 메서드에서 파라미터로 모델(Model), 커맨드 객체 등을 받아 파라미터로 받은 객체에 데이터를 저장하고 다시 뷰에 전달해 뷰에서 데이틀 사용할 수 있게 한다.

이렇게 사용하는 모델은 스프링을 사용한 웹 애플리케이션 개발에서 가장 기본이 되는 부분이다.

package mytest;

import java.util.HashMap;
import java.util.Map;

public class ModelUse {

	public static void main(String[] args) {
		Map<String,String> model = new HashMap<>();
		String sReturn = root(model);
		printData(sReturn, model);
	}
	
	public static String root(Map model) {
		model.put("name1", "홍길동");
		model.put("name2", "전우치");
		return "Hello";
	}
	
	public static void printData(String s, Map model) {
		String str1= (String)model.get("name1");
		System.out.println(str1);
		System.out.println("WEB-INF/views/"+s+".jsp");
		
	}
	
	

}

main을 톰캣 컨테이너라고 가정하자.

 

요청이 오면 Map 변수를 만들고 리퀘스트 매핑에 의해 메서드를 호출하면서 메서드에 객체 변수를 넘겨준다.

리퀘스트매핑의 호출에 의해 실행된 메서드는 파라미터로 받은 model에 데이터를 원하는 만큼 넣고 사용자에게 보여줄 JSP 페이지의 이름을 리턴한다.

 

다시 컨테이너는 리턴 받은 정보를 이용하여 뷰를 출력하는 메서드를 호출한다.

이때 페이지 이름과 페이지에서 사용할 정보를 파라미터로 모두 넘겨준다.

호출이 종료되면 지역 메서드가 끝났으므로 객체 변수는 메모리에서 제거된다.

 

자바는 리턴을 하나밖에 못하기 때문에 여러 개의 값을 리턴해야 할 때는 참조형 변수를 이용한 이런 형식을 만들어서 사용한다.

 

package com.study.springboot;

import java.util.ArrayList;
import java.util.List;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class MyController {

	@RequestMapping("/")
	public @ResponseBody String root() throws Exception{
		return "Model & View";
	}
	
	@RequestMapping("/test1")
	public String test1(Model model) {
		//Model 객체를 이용해서, view로 Data 전달
		// 데이터만 설정이 가능
		model.addAttribute("name", "홍길동");
		
		return "test1";
	}
	
	@RequestMapping("/mv")
	public ModelAndView test2() {
		// ㄷ이터와 뷰를 동시에 설정 가능
		ModelAndView mv = new ModelAndView();
		
		List<String> list = new ArrayList<>();
		
		list.add("test1");
		list.add("test2");
		list.add("test3");
		
		mv.addObject("lists", list);  //jstl로 호출
		mv.addObject("ObjectTest", "테스트입니다."); //jstl로 호출
		mv.addObject("name", "홍길동"); //jstl로 호출
		mv.setViewName("view/myView");
		
		return mv;
	}
}

 

url 호출이 /인 경우에는 내용 자체만 스트링으로 리턴한다.

RESTful API로 사용할 경우에 이 부분에 xml이나 json 데이터를 만들어 리턴하면 된다.

 

/test1으로 url 호출이 들어오면 test1 메서드가 실행된다. 이 메서드는 모델을 파라미터로 받아 해당 객체에 값을 키, 밸류 값으로 추가하고 있다.

 

리턴으로 스트링을 넘기면 뷰 리졸버가 test1.jsp를 찾아온다.

이때 test.jsp에서는 조금 전 모델의 데이터를 사용하여 뷰에 데이터를 출력할 수 있게 된다.

 

/mv로 url 호출이 들어오면 test2 메서드가 실행된다.

이 메서드에서는 스트링 값을 리턴하지 않고 ModelAndView 라는 객체 변수를 만들어 데이터 정보를 추가하고, 뷰 정보까지 함께 담아 객체 자체를 리턴한다.

'Back-end' 카테고리의 다른 글

Spring 용어정리 (2)  (0) 2021.08.11
Spring 용어정리 (1)  (0) 2021.08.10
서블릿(Servlet)  (0) 2021.08.08
웹 프로그래밍  (0) 2021.08.07
SQL 문법 (4)  (0) 2021.08.01