Spring 개념 정리

devlsn96·2024년 10월 14일
0

🍼 들어가기 전에...

프레임워크와 라이브러리의 차이

Framework란?

  • '뼈대나 근간'을 이루는 코드들의 묶음(프레임)으로 프로그램의 기본 흐름이나 구조를 정하고, 이 구조에 자신의 코드 추가하는 방식으로 개발할 수 있도록 하는 프로그래밍의 기본 틀을 의미한다.
    개발에 필요한 구조 제공 + 필요한 부분을 조립

라이브러리

  • 라이브러리란 자주 사용되는 로직을 재사용하기 편리하도록 잘 정리한 일련의 코드들의 집합(기능을 하는 부품)

🥗 Spring 이란?

스프링 프레임워크는 자바 플랫폼을 위한 오픈소스 애플리케이션 프레임워크로서 간단히 스프링 Spring 이라고도 불린다.

  • 동적인 웹사이트를 개발하기 위한 여러가지 서비스를 제공하고 있다.

Spring과 Spring boot의 차이

특징스프링 프레임워크(Spring Framework)스프링 부트(Spring Boot)
목적 및 용도포괄적인 엔터프라이즈급 애플리케이션 개발 프레임워크마이크로서비스와 빠른 애플리케이션 개발을 위한 도구
설정 방식XML 또는 애너테이션 기반 설정자동 설정과 기본 설정 제공, 간편한 설정 (application.yml)
내장 서버 제공내장 서버 미포함, 외부 서블릿 컨테이너에 배포Tomcat, Jetty 등 내장 서버 포함, 독립 실행 가능
의존성 관리라이브러리와 버전을 수동으로 설정스프링 부트 스타터로 간편하게 의존성 추가 및 버전 관리
배포 방식주로 WAR 파일 형태로 배포하여 서버에 배포JAR 또는 WAR 파일로 배포, java -jar로 실행 가능

🛒 STS 설치하기

  • 스프링 홈페이지에서 개발 편집기에 맞는 STS를 설치하고 실행하면된다.
  • STS (Spring Tool Suit) : 스프링 개발에 맞게 사용할 수 있게 하는 스프링에서 제공하는 툴이다.

1. spring boot project 만들기

https://start.spring.io/ 에서 환경설정을 하면 좀 더 간편하게 프로젝트를 생성할 수 있다.

1) project, Language, Spring Boot 버전, 파일 이름 설정, 배포, 자바 버전, 라이브러리 추가(dependencies)를 선택한 후, GENERATE를 눌러 다운로드하여 작업할 workspace로 이동해준다.

Project

1. 사용할 빌드 툴을 Maven이나 Gradle 중 선택할 수 있다.
2. 프로젝트에 필요한 의존성을 관리하고 빌드 라이프 사이클을 관리해주는 툴이다.
Maven과 Gradle

2) STS프로그램에서 작업할 workspace를 실행하여. Gradle project를 가져온다.


  • 이때, 해당 디렉토리의 파일경로를 연결하여 프로젝트 생성을 'finish'한다.
  • 생성된 파일의 구조는 위의 그림과 같다.

3) 브라우저에서 렌더링 실행을 위한 html 파일 생성

이를 위해서는 *.html등 (FE)이 필요한데, 이클립스에서는 "자바 웹 개발 플러그인"을 설치해야 한다.

  • 설치 후, 기본 선택된 것들을 select 후, 이클립스 재시작한다.

    Thymeleaf

    • FE영역에서 자바에서는 Thymeleaf를 사용하여 동적 웹 페이지를 쉽고 간편하게 구현할 수 있다.
    • <html xmlns:th="http://www.thymeleaf.org"> 를 파일 상단에 추가하여 "thymeleaf"를 사용한다를 선언하여 사용한다.
    • thymeleaf를 사용하면 BE의 데이터를 <p th:text="'hello ' + ${name}">Hello! empty!!</p> 간단하게 표현할 수 있다.
      만약, thymeleaf 의 표현식에 한글이 있다면 application.properties에서 한글 인코딩 세팅을 할 수 있고, thymeleaf는 서버재실행 시 원활한 캐시비우기를 위헤 다음과 같이 설정 할 수 있다.
      # 한글 세팅
      server.servlet.encoding.charset=UTF-8
      server.servlet.encoding.enabled=true
      server.servlet.encoding.force=true
      # 렌더링시 캐시 비우기
      spring.thymeleaf.cache=false

4) Controller파일 생성

  • @Controller 어노테이션으로 컨트롤러 역할을 부여한다.
  • @PostMapping("url")이나 @GetMapping("url") get방식이나 post방식으로 해당 url을 매핑을 한다.

viewResolver

  • 컨트롤러에서 리턴값으로 문자를 반환하면, "뷰 리졸버"가 화면을 찾아서 처리한다.
  • 스프링 부트 템플릿 엔진은 기본적으로 viewName으로 매핑된다.
  • 매핑 경로는 resouces/templates/ + {viewName} + .html 이다.
  • viewResolver는 실행할 뷰를 찾는 역할이고, 컨트롤러가 리턴한 뷰 이름에 해당하는 뷰 객체를 매핑하는 역할이다.
  • forward방식, redirect방식의 페이지 이동에 대한 리턴 값을 표현할 수 있다.
    • forward방식 : return "forward:/hello" 기본값
    • redirect방식 : return "redirect:/hello"

Model이란 무엇인가

  • Controller에서 데이터를 Model에 담는다.
  • view 는 Model에 담겨있는 데이터만 쏙쏙 골라서 화면에 바인딩해준다.
  • Model 객체는 컨트롤러에서 데이터를 생성해
    이를 view에 전달하는 역할을 한다.
  • HashMap의 형태를 갖고 있고 key와 value 값을 저장한다.
  • Servlet의 request.setAttribute()와 비슷한 역할을 한다.
    🧱 ModelAndView
  • model에서 view영역이 좀 더 확장된 형태로, Model과 View를 동시에 설정이 가능하며
    컨트롤러는 ModelAndView객체만 리턴하지만, Model과 View가 모두 리턴 가능하다.
  • addObject("key", value);로 key, value값을 가져온다.
    Model vs ModelAndView
    Model은 데이터만 저장하고,
    ModelAndView은 데이터와 이동하고자 하는 View Page를 같이 저장한다.
    🧱 @RequestMapping
  • @RequestMapping 어노테이션은 활용이 매우 유연하고, 실용적인 컨트롤러를 만들 수 있다. (@PostMapping이든, @GetMapping이든)
  • 해당 url이 호출되면 이 메서드가 호출된다.
    (메서드의 이름은 임의로 짓고 보통, 어노테이션을 기반으로 동작한다.)
  • @RequestParam : param 값을 받아온다.
  • required : 파라미터값의 필수 여부를 지정한다. (true일 경우, 필수 default값, false 일 경우, 필수 아님)
  • defaultValue : 파라미터 값이 없을 경우에 기본으로 들어갈 값이다.
@GetMapping("url")
	public String methodName(@RequestParam(value = "name", required = false, defaultValue = "default Spring") String name, Model model) {
		model.addAttribute("name", name);
		return "template";
	}

5) 서버실행

  • 스프링에서는 Embed Tomcat라이브러리가 내장되어 있기 때문에 Boot Dashboard창에서 간편하게 서버 실행이 가능하다.
  • 만약, 사용 중인 port번호가 중복인 경우에 브라우저 랜더링 실행이 어려울 수 있으므로 application.properties파일에서 server.port=로 톰캣 서버의 포트번호를 수정할 수 있다.

    🎉 Embed Tomcat

    • 스프링 부트는 내장형 톰캣을 가지고 있기 때문에 별도의 톰캣을 설정할 필요가 없어졌으며,
    • 따라서, 독립적으로 실행가능한 jar로 손쉽게 배포가 가능해졌다.

🍖 Spring MVC

DispatcherServlet

  • 스프링 MVC도 프론트컨트롤러 패턴으로 구현되어 있다.
  • 스프링 MVC의 프론트컨트롤러가 바로 "디스패처 서블릿 DispatcherServlet "이다.
  • DispatcherServlet도 부모 클래스에서 HttpServlet을 상속받아서 사용하고 서블릿을 동작한다.
    • 상속 구조 : DispatcherServlet -> FrameworkServlet -> HttpServletBean -> HttpServlet

일반적인 웹 애플리케이션 계층 구조

  • Controller : 웹 MVC의 컨트롤러 역할
  • Service : 핵심 비지니스 로직을 구현
  • 레퍼지토리 : 데이터베이스에 접근, 도메인 객체를 DB에 저장하고 관리
  • 도메인 : 비지니스 도메인 객체이다. (회원, 주문, 쿠폰 등 비지니스 로직 처리를 한다.)

Service

  • 서비스 레이어 단에서 세분화된 비지니스 로직을 처리하는 객체로, 비지니스 로직이 들어가는 부분이다.
  • Controller가 Request를 받으면,
    MemberService mService = new MemberService();
    적절한 Service에 전달하고,
    전달받은 Service는 비지니스 로직회원가입,전체회원조회을 처리한다.
    public class MemberService {
    	MemberRepository memberRepository = new MemoryMemberRepository();
    	// 회원가입
    	public int join(Member member) {
    		memberRepository.save(member);
    		return member.getId();
    	}
    	// 전체 회원 조회
    	public List<Member> findMembers(){
    		return memberRepository.findAll();
    	}
    }
  • DAO로 데이터베이스를 접근하고 DTO로 데이터를 전달받은 다음, 적절한 처리를 해 반환한다.
    • db데이터를 저장할 DTO에서는 일단, static영역에서 메모리를 저장하였다. (db를 커넥션한 클래스로의 코드 전환이 가능하기 때문에 Repository를 인터페이스로 설계한다.)
// MemberRepository 인터페이스로부터 상속받은
public class MemoryMemberRepository implements MemberRepository{
	// static영역에 메모리 사용 (서버 기동시, 캐시 삭제)
	private static Map<Integer, Member> store = new HashMap<>();
	private static int sequence = 0;
	@Override
	public Member save(Member member) {
		member.setId(++sequence);
		store.put(member.getId(), member);
		return member;
	}
	@Override
	public List<Member> findAll() {		
		return new ArrayList<>(store.values());
	}
}
  • JDBC 사용한 DAO

    • JDBC를 이용하여 db커넥션을 하는 레포지토리을 이용하기 위해서는 해당 사이트에서 라이브러리를 가져와 build.gradle에 등록해야 한다.
    implementation 'org.springframework.boot:spring-boot-starter-jdbc'
    implementation group: 'com.oracle.database.jdbc', name: 'ojdbc6', version: '11.2.0.4'
    • application.properties에서 oracle db계정 등록하고,
    # dbms
    spring.datasource.url=jdbc:oracle:thin:@localhost:1521:xe
    spring.datasource.driver-class-name=oracle.jdbc.OracleDriver
    spring.datasource.username=spring
    spring.datasource.password=spring
    • 레포지토리를 작성한다.
    private final DataSource dataSource;
    public JdbcMemberRepository(DataSource dataSource) {
    	this.dataSource = dataSource;
    }
    
    @Override
    public Member save(Member member) {
    	String sql = "INSERT INTO MEMBER VALUES (member_seq.nextval, ?)";		
    	Connection conn  = null;
    	PreparedStatement pstmt = null;
    	ResultSet rs = null;
    	try {
    		conn = dataSource.getConnection();
    		String generatedColumns[] = {"ID"};
    		pstmt = conn.prepareStatement(sql, generatedColumns);
    		pstmt.setString(1, member.getName());
    		pstmt.executeUpdate();
    		rs = pstmt.getGeneratedKeys();
    		if (rs.next()) {
    			member.setId(rs.getInt(1));
    		}
    	} catch (Exception e) {
    		e.printStackTrace();
    	}
    	return member;
    }
    @Override
    public List<Member> findAll() {
    	String sql = "SELECT * FROM MEMBER";
    	
    	Connection conn  = null;
    	PreparedStatement pstmt = null;
    	ResultSet rs = null;
    	List<Member> members = new ArrayList<>();
    	try {
    		conn = dataSource.getConnection();
    		pstmt = conn.prepareStatement(sql);
    		rs = pstmt.executeQuery();
    		while (rs.next()) {
    			Member member = new Member();
    			member.setId(rs.getInt("id"));
    			member.setName(rs.getString("name"));
    			members.add(member);
    		}			
    	} catch (Exception e) {
    		e.printStackTrace();
    	}
    	return members;
    }
  • JPA 사용한 DAO

  • DAO를 작성하기 전에, build.gradleimplementation 'org.springframework.boot:spring-boot-starter-data-jpa'라이브러리를 등록한다.

  • application.properties에서 jpa 설정 등록을 한다.

#jpa 설정
spring.jpa.show-sql = true
spring.jpa.hibernate.ddl-auto = none
  • dto에서 db데이터 관리를 위한 jpa 설정이 필요하다.
// jpa가 관리하는 dto클래스라고 지정
@Entity
public class Member {
	// 아이디 지정 (PK값, seq)
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "mySequence")
    @SequenceGenerator(name = "mySequence", sequenceName =  "member_seq", allocationSize = 1)
    private int id;
   	private String name;
    ~중략~
  • DAO영역인 레포지토리에 작성한다. 이때 select되는 db테이블은 dto클래스의 이름과 같게 한다.
private final EntityManager em;
public JpaMemberRepository(EntityManager em) {
	this.em = em;
}
@Override
public Member save(Member member) {
	em.persist(member);
    return member;
}
@Override
public List<Member> findAll() {
    return em.createQuery("select m from Member m", Member.class).getResultList();
}
  • service객체에서는 @Transactional어노테이션 선언이 필요하다.

    JPA
    JPA는 기존의 반복 코드는 물론이고, 기본적인 SQL도 JPA가 직접 만들어서 실행해 준다.
    JPA를 사용하면, SQL과 데이터 중심의 설계에서
    객체 중심의 설계로 패러다임을 전환할 수 있다.
    JPA를 사용하면, 개발 생산성을 크게 높일 수 있다.

Spring container란?

  • 스프링 컨테이너는 자바 객체의 생명 주기를 관리하며, 생성된 자바 객체들에게 추가적인 기능을 제공하는 역할을 한다. 여기서 말하는 자바 객체를 스프링에서는 "빈(Bean)" 이라고 부른다.
  • 개발자는 객체를 생성하고 소멸할 수 있는데, 스프링 컨테이너가 이 역할을 대신해 준다.
  • 즉, 제어의 흐름을 외부에서 관리하는 것이다.
  • 또한, 객체들 간의 의존관계를 스프링 컨테이너가 런타임과정에서 알아서 만들어 준다.
  • 스프링은 실행시 객체들을 담고있는 Container가 있다.

🍰 Spring 방식으로 작업한다는 것

  • Controller에서 Service를 호출하기 위해MemberService mService = new MemberService();방식으로 객체를 호출하는 것이 직관적으로 이해하기 쉬울 수 있으나, Spring 프레임워크에서 기본적으로 가지고 있는 "스프링 컨테이너"라는 특성때문에 자바 객체 (스프링 빈)을 등록하는 방식으로 제어의 흐름을 관리한다.

    • 스프링 컨테이너에 스프링 빈을 등록할 때는 기본적으로 싱글톤으로 등록한다. (유일하게 하나만 등록해서 공유한다) 따라서, 같은 스프링 빈이면 모두 같은 인스턴스다.
    • Component scan의 대상은 main 메서드가 있는 class의 동일 패키지 또는 하위 패키지만 Spring이 scan을 하는 대상이 된다. (당연히 설정을 통해서 다른 패키지의 클래스도 있지만 그렇지는 말자..) @ComponentScan로 선언되어 있다..
  • 가져올 스프링 빈을 선언하여 잠궈주고, (해당부분에서 변경할 객체를 변경할 필요가 없다.)
    private final MemberService memberService;

  • Controller가 생성될때 생성자를 호출해준다. (가져올 service 객체까지 생성해서 자동으로 호출해준다.)

    • @Autowired를 선언해주면, MemberController를 생성하면서 스프링이 memberService와 연결을 해준다.

      @Autowired

      필요한 의존 객체의 "타입"에 해당하는 빈을 찾아 주입한다. 그리고 Autowired는 기본값이 true이기 때문에 의존성 주입을 할 대상을 찾지 못한다면 애플리케이션 구동에 실패한다.

    @Autowired
	public MemberController(MemberService memberService) {
		this.memberService = memberService;
	}
  • 서버 기동시에 해당 부분이 연결하는 작업이 일어나는데, 서버 기동시에, 연결실패한다면, 에러가 발생하고 서버 기동 자체가 되지 않는다. (error)
  • 생성자에 @Autowired가 있으면, 스프링이 연관된 객체를 스프링 컨테이너에서 찾아서 넣어준다. 이러한 객체 의존관계를 외부에서 넣어주는 것을 DI(Dependency Injection), 의존성 주입이라 한다.

    DI(Dependency Injection), 의존성 주입

    의존성 주입이란, 객체간의 의존성이 존재할 경우 개발자가 직접 객체를 생성하거나 제어하는 것이 아니라, 제어반전에 의하여 특정 객체에 필요한 다른 객체를 프레임워크가 자동으로 연결시켜 주는 것을 말한다.
    의존성 주입을 하는 방법에는 3가지 방법이 있다. Field Injection (필드 주입), Setter Injection (수정자 주입), Constructor Injection (생성자 주입)

📖 제어의 역전 (Ioc, Inversion of Control)

  • 개발자가 프레임워크의 기능을 호출하는 형태가 아니라, 프레임워크가 개발자의 코드를 호출하기 때문에 개발자는 전체를 직접 구현하지 않고 자신의 코드를 부분적으로 "끼워넣기"하는 형태로 구현할 수 있다. 이는 개발자로 하여금 구현하고자 하는 특정 분야의 기능에 집중할수 있도록 한다.
  • 프레임워크가 객체의 생성, 소멸과 같은 라이프 사이클을 관리하며 스프링으로부터 필요한 객체를 얻어올 수도 있다.
  • 객체의 의존성을 역전시켜 객체간 결합도를 줄이고 유연한 코드를 작성할 수 있게 하여 가독성 및 코드 중복, 유지 보수를 편하게 할 수 있게 한다.

📖 POJO (Plain Old Java Object, 단순한 자바 오브젝트)

POJO란, 객체 지향적인 원리에 충실하면서 환경과 기술에 종속되지 않고, 필요에 따라 재활용 될 수 있는 방식으로 설계된 오브젝트를 말한다. 그러한 POJO에 애플리케이션의 핵심 로직과 기능을 담아 설계하고 개발하는 방법을 POJO프로그래밍이라고 한다.

HTTP 요청 파라미터

  • 스프링에서 HTTP 요청 메시지를 통해 클라이언트에서 서버로 데이터를 전달하는 방법

1. 반환 타입이 없으면서, 응답에 값을 직접 넣는 방식

  • 반환 타입이 없으면서, 응답에 값을 직접 넣으면 view 조회하지 않아도 된다.
@RequestMapping("/request-param-v1")
public void requestParam1(HttpServletRequest request, HttpServletResponse response) throws IOException {
	String username = request.getParameter("username");
	int age = Integer.parseInt(request.getParameter("age"));
	response.getWriter().write("ok");
}

2. 스프링에서 제공하는 어노테이션 이용하는 방식

  • @RequestParam(스프링이 제공하는 어노테이션)을 이용하면 파라미터의 이름으로 바인딩할 수 있다.
  • @ResponseBody를 이용하여 view 조회를 무시하고, HTTP message body에 직접 해당 내용 입력할 수 있다.
@ResponseBody
@RequestMapping("/request-param-v2")
public String requestParam2(@RequestParam("username") String memberName, @RequestParam("age") int memberAge) {
	return "ok";
}
  • 만약, HTTP 파라미터 이름이 변수 이름과 같으면 (@RequestParam(name="")) 생략 가능하다.
@ResponseBody
@RequestMapping("/request-param-v3")
public String requestParam3(@RequestParam String username, @RequestParam int age) {
	return "ok";
}

만약에 이클립스에서 서버 기동시, 파라미터를 받아오지 못하는 이클립스 오류가 난다면, 설정 > Java > Compiler 에서 해당부분을 적용시켜보면 된다.

  • 더 나아가, String, int 등의 "단순한 타입"이라면, @RequestParam도 생략가능하다.
@ResponseBody
@RequestMapping("/request-param-v4")
public String requestParam4(String username, int age) {
	return "ok";
}
  • required속성 : true일 경우, 반드시 파라미터값이 들어와야 한다. "기본값"
    • null을 int에 입력하는 건 불가능하기 때문에 int는 Integer로 변경해야한다.
@ResponseBody
@RequestMapping("/request-param-required")
public String requestParamRequired(@RequestParam(required = true) String username, @RequestParam(required = false) Integer age) {
	return "ok";
}
  • defaultValue속성 : 입력값이 없을때 값을 지정할 수 있다.
    - int값으로 받아와도 defaultValue는 문자열로 받아온다.
@ResponseBody
@RequestMapping("/request-param-default")
public String requestParamDefault(@RequestParam(defaultValue = "guest") String username, @RequestParam(defaultValue = "-1") int age) {
	return "ok";
}
  • Map의 형태로 key값을 파싱할 수있다. (Key=value)형태
@ResponseBody
@RequestMapping("/request-param-map")
public String requestParamMap(@RequestParam Map<String, Object> paramMap) {
	return "ok";
}

lombok 라이브러리

  • 자바(Java) 프로그래밍 언어에서 사용되는 라이브러리로, 반복적인 코드를 줄이고, 개발자가 더 간결한 코드 작성을 할 수 있도록 도와준다.

  • 롬복 라이브러리는 어노테이션 기반의 코드를 자동 생성해 주는 기능을 제공하여, gettersetter, toString(), equals(), hashCode() 등의 메서드를 직접 작성할 필요 없이 코드에 추가할 수 있게 한다.

  • 이를 통해 자바 코드의 가독성과 유지보수성을 크게 높일 수 있다.

    lombok 라이브러리 설치하기

    • 라이브러리에서 lombok을 찾아 "Run as" 클릭 후, sts에 설치한다.
  • request 요청 파라미터를 받아 필요한 객체를 생성해 그 객체에 값을 넣어주어야 한다.

@ResponseBody
@RequestMapping("/model-attribute-v1")
public String modelAttributeV1(
	@RequestParam String username, @RequestParam int age) {
	HelloData helloData = new HelloData();
	helloData.setUsername(username);
	helloData.setAge(age);
return "ok";
}
  • 그러나, 스프링에서 제공하는 기능인 @ModelAttribute를 사용하면 요청 파라미터를 객체에 담아주는 것을 완전히 자동화하여 요청 파라미터의 이름으로 객체의 프로퍼티를 찾아서 setter를 호출해서 파라미터의 값을 바인딩 한다.
@ResponseBody
@RequestMapping("/model-attribute-v2")
public String modelAttributeV2(@ModelAttribute HelloData helloData) {
	return "ok";
}
  • @RequestParam이 생략 가능한 것과 마찬가지로 @ModelAttribute 역시, 생략가능하다. String이나, int와 같은 단순한 타입을 요청 받아온 경우는 @RequestParam를 생략한 것이고 객체 타입으로 요청 받아온 경우는 @ModelAttribute를 생략한 것이다.
@ResponseBody
@RequestMapping("/model-attribute-v3")
public String modelAttributeV3(HelloData helloData) {
	return "ok";
}

ModelAndView로 data와 view를 같이 리턴하기

@RequestMapping("/response-view-v1")
public ModelAndView responseViewV1() {
	ModelAndView mav = new ModelAndView("response/hello").addObject("data","hello!!!");
		return mav;
	}

Controller에서 String을 반환하는 경우

  • view나 HTTP body에 메시지를 리턴하는데,
    @ResponseBody가 없으면, response/hello로 뷰 리졸버가 실행되어서 뷰를 찾아 렌더링한다. (templates/response/hello.html으로 이동)
    @ResponseBody가 있으면 뷰 리졸버를 실행하지 않고, HTTP 메시지 바디에 직접 response/hello 라는 문자가 입력 된다.
    (뷰 템플릿을 사용하는 것이 아닌, HTTP 메시지 바디에 작업 응답 데이터를 출력한다.)
profile
Quantum Jump to class for java….

0개의 댓글