wep app → rest api → full stack → Micro Services
spring 및 spring boot는 생산성을 크게 높여줌.
start.spring.io
spring initializr
File > Import > Maven > Existing Maven Projects > 아까 다운 받은 파일 > finish
프로젝트 폴더 learn-spring-framework 클릭.
src/main/java → 패키지 com.in28minutes 어쩌고 우클릭 > new > class
아래와 같이 입력하기
var marioGame = new MarioGame();
var gameRunner = new GameRunner();
gameRunner.run();
위와 같은 코드 작성 뒤 command+1을 누르면 Create class ‘MarioGame’ 선택 가능함.
package 이름에 .game만 추가한 뒤 finish를 누르면 패키지 안에 패키지가 생성됨.
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();
}
}
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에 정의한대로 결과가 잘 출력이 됨.
****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 코드를 수정해줘야함. → 코드에 강하게 결합되어있음.
How much work is involved in changing something?
무언가를 변경하는 데 얼마나 많은 작업이 관련되어 있는지
- 자동차 엔진과 바퀴 (교체 차이)
- 노트북과 컴퓨터 (휴대성)
Coupling is **even more important** in building ****great software.****
💡 가능한 한 코드를 적게 변경하면서 기능을 변경할 수 있어야 함.변화
GameRunner 클래스가 직접 게임 클래스와 상호작용하는 것이 아니라 GamingConsole과 상호작용하도록 함.
인터페이스 = 특정 클래스 세트에서 수행할 수 있는 공통된 작업
헉 자바 인터페이스!
.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();
}
}
실행 성공
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을 만들어주면 오류가 뜨는데, 클릭하고
`import org.springframework.context.annotation.Configuration;` 를 해주면 됨.
이 configuration 클래스가 하나 이상의 bean 메서드를 선언함을 나타냄.
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 컨텍스트 생성.
성공적으로 Configuration 파일을 사용해 Spring 컨텍스트를 실행할 수 있게됨.
Spring에게 name 객체를 관리하도록 명령하려면
HelloWorldConfiguaration 클래스에 name 메서드를 만들어주고 bean을 import 함.
run하면 아래처럼 bean 이름이 뜬다..!
스프링이 관리하는 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"));
}
}
강의 전에 한 번 내가 먼저 만들어봄.
@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);
}
두 개의 스트링 메서드가 레코드에 자동으로 구현되었으며 인물에 설정된 특성의 값을 유지하고 있음.
address2
로 bean 이름을 사용자 지정해줄 수 있음. 지정해준 이름이 아닌 address로 출력하려고 하면 No bean named 'address' available
이라면서 오류 발생.
지정해준 이름으로 바꿔주면 정상적으로 출력됨.
스프링 컨텍스트에서 bean 다시 검색
System.out.println(context.getBean(Address.class));
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());
}
@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.
Spring Bean과 수명 주기를 관리함.
java 클래스를 만들고 설정을 만들면 IOC 컨테이너가 런타임 시스템을 만듦. → IOC = 제어의 역전
스프링 컨테이너 = 스프링 컨텍스트 = 스프링 IOC 컨테이너
대부분 애플리케이션 컨텍스트 사용 → 웹서비스, 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은 제한적
EJB
class JavaBean {
public JavaBean(){
private String text;
private int number;
}
제약
serializable
→ 인수 없는 생성자, getter와 setter 존재, serializable 인터페이스구현 = 클래스 인스턴스는 java bean이 됨.
POJO가 더 중요
Java bean = 세 가지 제약 존재
POJO = Plain Old Java Object
스프링 빈을 모두 나열하려면?
Arrays.stream(context.getBeanDefinitionNames()).forEach(System.out::println);
//메서드 참조
context.getBeanDefinitionNames()
: 이 레지스트리에 정의된 모든 이름 (String) 반환
**함수형 프로그래밍 사용**
stream 준비, .forEach
를 사용 → 스트림에 있는 모든 요소에 대해 System.out.println
실행
일치하는 단일 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로 지정했더니 예외가 발생하지 않게 됨
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이 변경되는 것을 확인할 수 있었다.
public Person person5Qualifier(String name, int age, @Qualifier("address3qualifier") Address address) { //name, age, address2
return new Person(name, age, address);
}
기본 address 대신 다른 것을 사용하려고 할때 한정자 Qualifier를 사용한다.
com.in28minutes.learnspringframework.helloworld;
라는 이름의 새로운 패키지를 생성하고, 아래 이미지와 같이 파일을 이동시켜줌.
Resource leak : context is never closed
경고 수정하기
var context = new AnnotationConfigApplicationContext(HelloWorldConfiguration.class);
try-with-resource
사용하기
try(var context = new AnnotationConfigApplicationContext(HelloWorldConfiguration.class)){ 구문 }
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);
}
}
}
}
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