Spring : Class 1

강다빈·2023년 11월 1일
0

backend

목록 보기
1/5

wep app → rest api → full stack → Micro Services

spring 및 spring boot는 생산성을 크게 높여줌.

Spring과 관련 있는 용어

  • Tight Coupling
  • Loose Coupling
  • Dependency Injection
  • IOC Container
  • Application Context
  • Spring Beans
  • Auro Wiring
  • Component Scan

Maven과 Java로 새 Spring Framework 프로젝트 생성하기

start.spring.io

spring initializr

  • spring 프로젝트 생성 사이트 maven 프로젝트 선택, 언어 java. 스크린샷 2023-09-07 오후 3.44.38.png

File > Import > Maven > Existing Maven Projects > 아까 다운 받은 파일 > finish

스크린샷 2023-09-07 오후 3.51.48.png

Java 게이밍 어플리케이션 시작하기

Iteration 1 : Tightly Coupled Java Code

  • GameRunner Class
  • Game classes : Mario, SuperContra, Pacman etc

자바 클래스를 생성해봅시다

프로젝트 폴더 learn-spring-framework 클릭.

src/main/java → 패키지 com.in28minutes 어쩌고 우클릭 > new > class

아래와 같이 입력하기

스크린샷 2023-09-07 오후 3.59.38.png

var marioGame = new MarioGame();
		var gameRunner = new GameRunner();
		gameRunner.run();

위와 같은 코드 작성 뒤 command+1을 누르면 Create class ‘MarioGame’ 선택 가능함.

스크린샷 2023-09-07 오후 4.03.16.png

package 이름에 .game만 추가한 뒤 finish를 누르면 패키지 안에 패키지가 생성됨.

스크린샷 2023-09-07 오후 4.05.23.png

public class MarioGame {

	public void up() {
		System.out.println("Jump");
	}
	public void down() {
		System.out.println("Go into a hole");
	}
	public void left() {
		System.out.println("Go back");
	}
	public void right() {
		System.out.println("Accelerate");
	}
}
public class GameRunner {
	MarioGame game;
	
	public GameRunner(MarioGame game) {
		this.game = game;
	}

	 
	public void run() {
		 System.out.println("Running game: " + game);
		 game.up();
		 game.down();
		 game.left();
		 game.right();
	}
}

스크린샷 2023-09-07 오후 4.15.56.png

Tightly Coupled

SuperContraGame이라는 클래스를 만들어서 컴파일하려고 하면 오류가 발생함. GameRunner(SuperContraGame game)으로 생성자를 새로 정의해줘야 하기 때문임.

****AppGamingBasic.java****

import com.in28minutes.learnspringframework.game.GameRunner;
import com.in28minutes.learnspringframework.game.MarioGame;
import com.in28minutes.learnspringframework.game.SuperContraGame;

public class AppGamingBasic {

	public static void main(String[] args) {
		
		//var marioGame = new MarioGame();
		var  superContraGame = new SuperContraGame();
		var gameRunner = new GameRunner(marioGame); //오류 발생
		gameRunner.run();
	}

}

**GameRunner.java**

package com.in28minutes.learnspringframework.game;

public class GameRunner {
	MarioGame game;
	
	public GameRunner(MarioGame game) { //SuperContraGame이 정의돼있지 않음.
		this.game = game;
	}

	 
	public void run() {
		 System.out.println("Running game: " + game);
		 game.up();
		 game.down();
		 game.left();
		 game.right();
	}
}

위 코드를 아래와 같이 바꿔줘야 사진처럼 제대로 컴파일 됨.

public class AppGamingBasic {

	public static void main(String[] args) {
		
		//var marioGame = new MarioGame();
		var  superContraGame = new SuperContraGame();
		var gameRunner = new GameRunner(superContraGame);
		gameRunner.run();
	}

}

// =============================

public class GameRunner {
	//MarioGame game;
	SuperContraGame game;
	public GameRunner(SuperContraGame game) {
		this.game = game;
	}

	 
	public void run() {
		 System.out.println("Running game: " + game);
		 game.up();
		 game.down();
		 game.left();
		 game.right();
	}
}

supercontragame.java에 정의한대로 결과가 잘 출력이 됨.

스크린샷 2023-09-07 오후 4.30.20.png

****SuperContraGame.java****

public class SuperContraGame {
	public void up() {
		System.out.println("Jump");
	}
	public void down() {
		System.out.println("Sit down");
	}
	public void left() {
		System.out.println("Go back");
	}
	public void right() {
		System.out.println("Shoot a bullet");
	}
}

다른 게임을 실행할 수 있도록 GameRunner 코드를 수정해줘야함. → 코드에 강하게 결합되어있음.

Coupling

How much work is involved in changing something?

무언가를 변경하는 데 얼마나 많은 작업이 관련되어 있는지

  • 자동차 엔진과 바퀴 (교체 차이)
  • 노트북과 컴퓨터 (휴대성)

Coupling is **even more important** in building ****great software.****

💡 가능한 한 코드를 적게 변경하면서 기능을 변경할 수 있어야 함.

Java 인터페이스를 도입하여 loose coupling 앱 만들기

Iteration 2 : Loose Coupling - Interfaces

  • GameRunner class
  • GamingConsole interface
    • Game classes : 마리오, 슈퍼콘트라, 팩맨 등

변화

GameRunner 클래스가 직접 게임 클래스와 상호작용하는 것이 아니라 GamingConsole과 상호작용하도록 함.

스크린샷 2023-09-07 오후 4.46.39.png

인터페이스 = 특정 클래스 세트에서 수행할 수 있는 공통된 작업

헉 자바 인터페이스!

.game 패키지 우클릭 > new > interface > GamingConsole

public class SuperContraGame implements GamingConsole

AppGamingBasic.Java를 변경하지 않아도 됨.

GameRunner.java의 코드를 수정해줌.

private GamingConsole game;
	public GameRunner(GamingConsole game) {
		this.game = game;
	}

MarioGame도 아래와 같이 코드를 수정해주면, 아까와는 달리 주석을 교체하는 것만으로 컴파일 결과를 바꿀 수 있음.

public class MarioGame implements GamingConsole
//MarioGame와 인터페이스 implements

public class AppGamingBasic {

	public static void main(String[] args) {
		
		var game = new MarioGame();
		//var game = new SuperContraGame();
		var gameRunner = new GameRunner(game);
		gameRunner.run();
	}

}

게임을 바꾸려할 때 GameRunner 클래스에는 변경이 필요없음. 특정 게임이 아닌 특정 인터페이스인 GameCondsole과만 연결되어있기 때문임.

pacman도 다른 게임들처럼 만들어줌

package com.in28minutes.learnspringframework.game;

public class PacManGame implements GamingConsole {

	public void up() {
		System.out.println("Up");
	}
	public void down() {
		System.out.println("Down");
	}
	public void left() {
		System.out.println("Go left");
	}
	public void right() {
		System.out.println("Go right");
	}
}

//======================================================
//PacManGame을 실행하도록 수정

import com.in28minutes.learnspringframework.game.GameRunner;
import com.in28minutes.learnspringframework.game.MarioGame;
import com.in28minutes.learnspringframework.game.PacManGame;
import com.in28minutes.learnspringframework.game.SuperContraGame;

public class AppGamingBasic {

	public static void main(String[] args) {
		
		var game = new PacManGame();
		//var game = new MarioGame();
		//var game = new SuperContraGame();
		var gameRunner = new GameRunner(game);
		gameRunner.run();
	}

}

실행 성공

스크린샷 2023-09-07 오후 10.45.00.png

GamingConsole = GameRunner의 dependencies

객체를 수동으로 직접 관리, 수정, 생성하는 대신 Spring 프레임워크가 하도록 함.

객체는 jvm에서 생성. jvm 안에 Spring

Spring이 name, age, person, person2, address 등을 관리하도록 함.

App01GamingBasic.java를 복사한 뒤 App02HelloWorld.java를 만들어줌.

public class App02HelloWorldSpring {

	public static void main(String[] args) {
		//1: Launch a Spring Context
		//2: Configure the things that we want Spring to manage - @Configuration
		
		 
	}

}

configuration 클래스를 만들어줌.

@Configuration을 만들어주면 오류가 뜨는데, 클릭하고

`import org.springframework.context.annotation.Configuration;` 를 해주면 됨.

이 configuration 클래스가 하나 이상의 bean 메서드를 선언함을 나타냄.

스크린샷 2023-09-07 오후 11.12.38.png

package com.in28minutes.learnspringframework;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class App02HelloWorldSpring {

	public static void main(String[] args) {
		//1: Launch a Spring Context 스프링 애플리케이션이나 컨텍스트를 실행하는 단계
		
		var context = new AnnotationConfigApplicationContext(HelloWorldConfiguration.class);
		
		//2: Configure the things that we want Spring to manage - @Configuration
		
		 
	}

}

Spring 컨텍스트 생성.

스크린샷 2023-09-07 오후 11.27.33.png

성공적으로 Configuration 파일을 사용해 Spring 컨텍스트를 실행할 수 있게됨.

Spring에게 name 객체를 관리하도록 명령하려면

HelloWorldConfiguaration 클래스에 name 메서드를 만들어주고 bean을 import 함.

스크린샷 2023-09-07 오후 11.30.06.png

  • 이 메서드는 bean을 생성하며, bean은 스프링 컨테이너에 의해 관리됨.

run하면 아래처럼 bean 이름이 뜬다..!

스크린샷 2023-09-07 오후 11.32.24.png

스프링이 관리하는 name 객체를 확인하려면 어떻게 해야할까?

package com.in28minutes.learnspringframework;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class App02HelloWorldSpring {

	public static void main(String[] args) {
		//1: Launch a Spring Context 스프링 애플리케이션이나 컨텍스트를 실행하는 단계
		
		var context = new AnnotationConfigApplicationContext(HelloWorldConfiguration.class);
		
		//2: Configure the things that we want Spring to manage - @Configuration
		//HelloWorldConfiguration - @Configuration
		//name - @Bean
		
		//3: Retrieving Beans managed by Spring
		//Spring이 관리하는 Bean 검색. bean의 이름을 부여하여 검색 가능.
		System.out.println(context.getBean("name"));
	}

}

스크린샷 2023-09-07 오후 11.37.28.png

Spring Java 설정 파일에서 더 많은 Java Spring Bean 만들기

강의 전에 한 번 내가 먼저 만들어봄.

@Bean
	public String name() {
		return "kchabin";
	}
	
	@Bean
	public int age() {
		return 20;
	}

지난 단계 : Spring context를 만들고 Spring이 사용자 지정 객체 1개를 관리하도록 함.

이번에 사용자 지정 클래스 객체를 관리하도록 함.

//데이터를 저장하고 관리하기 위한 간단하고 불변한 클래스
//이 두 개의 인수가 있는 인물 생성자가 자동으로 생성됨.
record Person(String name, int age) {};
@Bean
	public Person person() {
		return new Person("Chloe", 22);
	}

스크린샷 2023-09-08 오후 10.44.33.png

두 개의 스트링 메서드가 레코드에 자동으로 구현되었으며 인물에 설정된 특성의 값을 유지하고 있음.

스크린샷 2023-09-25 오후 1.27.15.png

스크린샷 2023-09-25 오후 1.27.38.png

address2 로 bean 이름을 사용자 지정해줄 수 있음. 지정해준 이름이 아닌 address로 출력하려고 하면 No bean named 'address' available 이라면서 오류 발생.

지정해준 이름으로 바꿔주면 정상적으로 출력됨.

검색할 수 있는 다양한 접근 방식 제공

스프링 컨텍스트에서 bean 다시 검색

System.out.println(context.getBean(Address.class));

기존 Spring bean과 관계 있는 새로운 Spring bean 만들기

  1. 메서드 직접 호출
record Person(String name, int age, Address address) {};
//매개변수 추가

@Bean
	public Person person() {
		return new Person("Chloe", 22, new Address("Gangnam", "Seoul") );
		 
	}
//하드코딩

//메서드로 호출
	@Bean(name="person2")
	public Person person2MethodCall() {
		return new Person(name(), age(), address());
		 
	}
  1. 매개변수 호출
@Bean(name="person3")
	public Person person3Parameters(String name, int age, Address address2) { //name, age, address2
		return new Person(name, age, address2);
		 
	}

한 개의 Spring Bean을 만드는데 다른 기존 Spring bean을 사용함.

address3를 만들어서 값을 좀 바꿔주고 실행하면 Address에 Bean이 address2, address3 두 개가 존재한다고 예외 발생.

Q1. Spring Container vs Spring Context vs IOC Container vs Application Context

Q2. Java Bean vs Spring Bean

Q5. spring is managing objects and performing auto-wiring.

  • But aren’t we writing the code to create objects?
  • How do we get Spring to create objects for us?

스프링 컨테이너

Spring Bean과 수명 주기를 관리함.

java 클래스를 만들고 설정을 만들면 IOC 컨테이너가 런타임 시스템을 만듦. → IOC = 제어의 역전

스프링 컨테이너 = 스프링 컨텍스트 = 스프링 IOC 컨테이너

스크린샷 2023-09-26 오전 1.48.18.png

  1. Bean Factory : basic 스프링 컨테이너
  2. Application Context → 가장 자주 사용
  • 엔터프라이즈 전용 기능이 있는 고급 Spring 컨테이너
  • Easy to use in web applications
  • Easy internationalization
  • Easy integration with Spring AOP

대부분 애플리케이션 컨텍스트 사용 → 웹서비스, rest API, 웹 어플리케이션

IOT → bean factory

class Pojo {
	private String text;
	private int number;
	public String toString() {
		return text + ":" + number;
	}
}

public class SpringBeanVsJavaBean {
	public static void main(String[] args) {
		Pojo pojo = new Pojo();
}

POJO = 모든 Java 객체

지금까지 강의에서 만든 모든 bean은 pojo, java bean은 제한적

Java Bean

EJB

  • 15년 전 가장 인기있는 아키텍처
class JavaBean {
	public JavaBean(){
	
	private String text;
	
	private int number;
}
  • 인수 생성자 x. java에서 자동 부여

제약

  • public no-arg constructor
  • getters and setters
  • serializable

인수 없는 생성자, getter와 setter 존재, serializable 인터페이스구현 = 클래스 인스턴스는 java bean이 됨.

POJO가 더 중요

Java bean = 세 가지 제약 존재

POJO = Plain Old Java Object

  • No constraints
  • Any java object is a POJO

Spring Bean

  • 객체 관리를 위해 IOC Container(Bean factory or Application Context)를 사용

Q3. How can I list all beans managed by Spring Framework?

스프링 빈을 모두 나열하려면?

Arrays.stream(context.getBeanDefinitionNames()).forEach(System.out::println); 
//메서드 참조

context.getBeanDefinitionNames() : 이 레지스트리에 정의된 모든 이름 (String) 반환

**함수형 프로그래밍 사용**

stream 준비, .forEach 를 사용 → 스트림에 있는 모든 요소에 대해 System.out.println 실행

Q4. What if multiple matching beans are available?

스크린샷 2023-09-27 오후 2.30.50.png

일치하는 단일 Bean이 있어야 하지만 address2, address3 의 두 개가 있음.

@Bean(name="address2") //Bean 이름을 사용자 지정 가능
	public Address address() {
		return new Address("KyeongbokPalace", "Seoul");
	}
	@Bean(name="address3") //Bean 이름을 사용자 지정 가능
	public Address address3() {
		return new Address("Hangang", "Seoul");
	}

일치하는 후보가 여러 개 → 예외 발생

한 후보를 default로 → @Primary : 어떤 후보가 가장 중요한지

person4 를 default로 지정해도 예외가 발생함. 코드를 읽어보니까 얘는 딱히 호출되는 것도 아닌 것 같아서 person3 를 default로 지정했더니 예외가 발생하지 않게 됨

스크린샷 2023-10-05 오후 12.31.25.png

package com.in28minutes.learnspringframework;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

//데이터를 저장하고 관리하기 위한 간단하고 불변한 클래스
//이 두 개의 인수가 있는 인물 생성자가 자동으로 생성됨.
record Person(String name, int age, Address address) {};
record Address(String firstLine, String city) {};

@Configuration
public class HelloWorldConfiguration {
	//여기서 Spring Bean을 정의할 수 있음. ->Spring에서 관리하는 것
	//설정 클래스에서 메서드를 정의하여 bean을 생성할 수 있음.
	
	@Bean
	public String name() {
		return "kchabin";
	}
	
	@Bean
	public int age() {
		return 20;
	}
	
	@Bean
	public Person person() {
		return new Person("Chloe", 22, new Address("Gangnam", "Seoul") );
		 
	}
	@Bean(name="person2")
	public Person person2MethodCall() {
		return new Person(name(), age(), address());
		 
	}
	@Primary
	@Bean(name="person3")
	public Person person3Parameters(String name, int age, Address address3) { //name, age, address2
		return new Person(name, age, address3);
		 
	}
	@Primary
	public Person person4Parameters(String name, int age, Address address) { //name, age, address2
		return new Person(name, age, address);
		 
	}

	
	@Bean(name="address2") 
	//@Primary
	public Address address() {
		return new Address("KyeongbokPalace", "Seoul");
	}
	@Bean(name="address3") //Bean 이름을 사용자 지정 가능
	@Primary
	public Address address3() {
		return new Address("Hangang", "Seoul");
	}
	
	 
}

처음엔 address2 를 기본으로 지정했다가 address3 를 기본으로 지정했더니 firstLine이 변경되는 것을 확인할 수 있었다.

스크린샷 2023-10-05 오후 12.33.18.png

스크린샷 2023-10-05 오후 12.33.46.png

💡 여러 개의 후보 bean이 있는 시나리오에서는 `@Primary` 를 사용하여 어떤 후보가 가장 중요한지 나타낼 수 있다.

Qualifier

public Person person5Qualifier(String name, int age, @Qualifier("address3qualifier") Address address) { //name, age, address2
		return new Person(name, age, address);
		 
	}

기본 address 대신 다른 것을 사용하려고 할때 한정자 Qualifier를 사용한다.

스크린샷 2023-10-05 오후 1.47.46.png

com.in28minutes.learnspringframework.helloworld; 라는 이름의 새로운 패키지를 생성하고, 아래 이미지와 같이 파일을 이동시켜줌.

스크린샷 2023-10-05 오후 2.13.54.png

Resource leak : context is never closed 경고 수정하기

var context = new AnnotationConfigApplicationContext(HelloWorldConfiguration.class);

try-with-resource 사용하기

try(var context = new AnnotationConfigApplicationContext(HelloWorldConfiguration.class)){ 구문 }

☕ 자바 Try With Resource 예외 처리

resource → DB, Network, File 등

이런 리소스들은 자바 내부에 위치한 요소들이 아니기 때문에, 이러한 프로세스 외부에 있는 데이터에 자바 코드에서 접근하려고 할 때 예외가 발생할 수 있음.

파일을 열고 내용을 쓰고 난 후 꼭 닫아주어야 함. ← 어떤 리소스를 사용하다가 다른 곳에서 같은 리소스에 접근하면 꼬일 수 있음.

import java.io.FileWriter;
import java.io.IOException;

public class Main {
    public static void main(String[] args) {
        FileWriter file = null;
        try {
            file = new FileWriter("data.txt");
            file.write("Hello World");
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            file.close(); 
            // 작업중에 예외가 발생하더라도 파일이 닫히도록 finally블럭에 넣음
			// 근데 close()가 예외를 발생시키면 문제가 됨
        }
    }
}

data.txt 라는 파일을 읽고 IOException 으로 예외처리, 작업 중 예외가 발생하더라도 파일이 닫히도록 finally 블록 활용.

그러나, 파일을 닫는 file.close() 코드 자체도 IOException 예외가 발생할 수 있어 이 부분도 예외처리를 해주어야 함.

→ finally 안에 try-catch 구문을 또 작성

import java.io.FileWriter;
import java.io.IOException;

public class Main {
    public static void main(String[] args) {
        FileWriter file = null;
        try {
            file = new FileWriter("data.txt");
            file.write("Hello World");
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
        	// close()에서 발생하는 예외를 처리하기 위해서 아래와같이 바꿀수도 있지만 코드가 복잡해져서 좋지않다.
            try {
                file.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

Try with Resource

JDK1.7부터 try-catch 문의 변형 문법이 추가됨.

주로 입출력 관련 클래스 사용 시 굉장히 유용함. 입출력에 사용한 객체를 자동으로 반환시켜주기 때문.

try (파일을 열거나 자원을 할당하는 명령문) {
     ...
}

try 블로에 괄호를 추가하여 ****파일을 열거나 자원을 할당하는 명령문****을 명시하면, 해당 블록이 끝나자마자 자동으로 파일을 닫거나 할당된 자원을 해제해줌.

위 코드 리팩토링

import java.io.FileWriter;
import java.io.IOException;

public class Main {
	public static void main(String[] args) {
		try(FileWriter file = new FileWriter("data.txt")){
				file.write("Hello World");
		} catch(IOException e) {
				e.printStackTrace();
		}
	}
}
// try 괄호 안에 두문장 이상 넣을 경우 ';'로 구분한다.
try(
	FileInputStream fis = new FileInputStream("a.txt");
    DataInputStream dis = new DataInputStream(fis)
) {
	
    while(true){
    	score - dis.readInt();
        System.out.println(score);
        sum += score;
    }
    
} catch (EOFException e){
    System.out.println("점수의 총합은 " + sum + "입니다.");
} catch (IOException ie){
    ie.printStackTrace();
}

AutoClosable 이라는 인터페이스를 구현하고 있어야 함.

Auto-Wiring

profile
SKT DEVOCEAN YOUNG 2기, Kubernetes Korea Group

0개의 댓글

관련 채용 정보