Spring이 필요한 이유?
Spring을 통해 다양한 애플리케이션(웹, REST API, Full Stack, Microservice) 빌드 가능.
쉽게 문제 디버깅할 수 있음.
Spring Boot에 대한 빠른 이해를 도움.
개발 생산성을 높이기 위해 사용!
Spring Framework: App 구축에 필요한 기능 제공 (“의존성 주입”과 “자동 연결”)
Spring Boot: Spring Framework를 쉽게 사용하기 위한 방법.
자동차-엔진: tight coupling
자동자-바퀴: loose coupling
코드는 지속적으로 변화하기 때문에, loose coupling을 사용하는 것이 바람직함.
가능한 한 코드 변경을 최소화하면서 기능을 변경할 수 있어야 함.
// Iteration 1. Tight Coupled Java Code
// GameRuner class
// Game classes: Mario, SuperContra, Pacman, etc.
package com.in28minutes.learn_spring_framework;
import com.in28minutes.learn_spring_framework.game.GameRunner;
import com.in28minutes.learn_spring_framework.game.MarioGame;
import com.in28minutes.learn_spring_framework.game.SuperContraGame;
public class AppGamingBasicJava {
public static void main(String[] args) {
var mariogame = new MarioGame();
var marioGameRunner = new GameRunner(mariogame);
marioGameRunner.run();
var superContraGame = new SuperContraGame();
var superContraGameRunner = new GameRunner(superContraGame);
superContraGameRunner.run();
}
}
package com.in28minutes.learn_spring_framework.game;
public class GameRunner {
private MarioGame game;
// MarioGame 타입의 인자만 받을 수 있는 상황
// Tight Coupling 상황
public GameRunner(MarioGame game) {
this.game = game;
}
public void run() {
System.out.println("Running game: " + game);
game.up();
game.down();
game.left();
game.right();
}
}
package com.in28minutes.learn_spring_framework.game;
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");
}
}
package com.in28minutes.learn_spring_framework.game;
public class SuperContraGame {
public void up() {
System.out.println("up");
}
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 클래스가 GamingConsole 인터페이스와 상호작용하고 각 Game 클래스가 GamingConsole 인터페이스를 도입
// Iteration 2. Loose Coupling - Interfaces
// GameRunner class
// GamingConsole interface
// - Game classes: Mario, SuperContra, Pacman, etc.
public class AppGamingBasicJava {
public static void main(String[] args) {
var mariogame = new MarioGame();
var marioGameRunner = new GameRunner(mariogame);
marioGameRunner.run();
var superContraGame = new SuperContraGame();
var superContraGameRunner = new GameRunner(superContraGame);
superContraGameRunner.run();
var pacmanGame = new PacmanGame();
var pacmanGameRunner = new GameRunner(pacmanGame);
pacmanGameRunner.run();
}
}
Running game: com.in28minutes.learn_spring_framework.game.MarioGame@6d311334
Jump
Go into a hole
Go back
Accelerate
Running game: com.in28minutes.learn_spring_framework.game.SuperContraGame@3d075dc0
up
Sit down
Go back
Shoot a bullet
Running game: com.in28minutes.learn_spring_framework.game.PacmanGame@448139f0
Up
Down
Left
Right
Process finished with exit code 0
package com.in28minutes.learn_spring_framework.game;
public class GameRunner {
private GamingConsole game;
// GamingConsole 인터페이스를 통해
// GameRunner 코드를 변경하지 않고
// Game 종류에 상관없이 유연하게 대처 가능.
public GameRunner(GamingConsole game) {
this.game = game;
}
public void run() {
System.out.println("Running game: " + game);
game.up();
game.down();
game.left();
game.right();
}
}
package com.in28minutes.learn_spring_framework.game;
// GameRunner가 Game 종류에 상관없이 일관된 작동할 수 있도록 함.
public interface GamingConsole {
void up();
void down();
void left();
void right();
}
각 Game 클래스는 GamingConsole 인터페이스를 구현하고 인터페이스의 추상 메소드를 실제 구현한다.
package com.in28minutes.learn_spring_framework.game;
// GamingConsole 인터페이스를 구현하고 추상 메소드 구현.
public class MarioGame implements GamingConsole {
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");
}
}
package com.in28minutes.learn_spring_framework.game;
// GamingConsole 인터페이스를 구현하고 추상 메소드 구현.
public class SuperContraGame implements GamingConsole {
public void up() {
System.out.println("up");
}
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");
}
}
package com.in28minutes.learn_spring_framework.game;
// GamingConsole 인터페이스를 구현하고 추상 메소드 구현.
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("Left");
}
public void right() {
System.out.println("Right");
}
}
기존: JVM 내부에서 실행되는 객체 생성 코드를 사용자가 직접 관리
Spring: 프레임워크가 사용자 대신 객체 생성 및 결합할 수 있도록 함.
JVM에서 Spring context를 생성하고, Spring이 관리할 수 있는 Spring Bean을 생성. - @Configuration, @Bean 활용.
Spring이 관리하는 모든 것은 Spring Bean.
package com.in28minutes.learn_spring_framework;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import java.util.Arrays;
// Spring & Spring Bean
public class App02HelloWorldSpring {
public static void main(String[] args) {
// 1. Launch a Spring context
var context =
new AnnotationConfigApplicationContext(HelloWorldConfiguration.class);
// 2. Config things we want Spring to manage
// HelloWorldConfiguration - @Configuration
// name - @Bean
// 3. Retrieving Beans managed by Spring
System.out.println(context.getBean("name"));
System.out.println(context.getBean("age"));
System.out.println(context.getBean("person"));
System.out.println(context.getBean("person2MethodCall"));
System.out.println(context.getBean("person3Parameters"));
System.out.println(context.getBean("customBean"));
// getBean을 통해 Spring Bean의 이름을 통해 Bean 불러올 수 있음.
System.out.println("\n" + context.getBean(Address.class));
// getBean을 통해 Spring Bean의 타입을 지정해 Bean 불러올 수 있음.
System.out.println("\n" + context.getBean(Person.class) + "\n");
// 같은 타입의 객체가 여러개 발견되어 에러 발생. @Primary로 우선순위 부여해 해결.
// get all names of beans by functional programming
Arrays.stream(context.getBeanDefinitionNames())
.forEach(System.out::println); // method reference
// Qualifier로 특정
System.out.println("\n" + context.getBean("person5Qualifier") + "\n");
}
}
package com.in28minutes.learn_spring_framework;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
// record?
// immutable 데이터 객체를 간결하게 만들어 가독성 높임.
// 필드 정의 시, 메소드 자동 생성(eqauls, hashCode, toString, ...)
// setter, getter, constructor 자동 생성.
// 객체 간 불변 데이터 전달 목적. Data Transfer Object 표현에 적합.
record Person (String name, int age, Address address) { }
record Address (String firstLine, String city) { }
@Configuration
public class HelloWorldConfiguration {
// 2. Config things we want Spring to manage
// HelloWorldConfiguration - @Configuration
// name - @Bean
// 1+ Bean method required
@Bean
public String name() {
return "itisyijy";
}
@Bean
public int age() {
return 25;
}
@Bean
@Primary
public Person person() {
var person = new Person("Cona", 8, new Address("Gangnam-gu", "Seoul"));
return person;
}
// Bean 재활용해 새로운 Bean 생성 가능.
@Bean
public Person person2MethodCall() {
// method call을 통해 Bean 활용하는 접근.
var person = new Person(name(), age(), address());
return person;
}
// Bean 재활용해 새로운 Bean 생성 가능.
@Bean
public Person person3Parameters(String name, int age, Address customBean) {
// 파라미터로 Bean 활용하는 접근.
// 파라미터가 자동으로 파라미터명에 해당하는 Bean을 자동 연결해 새로운 Bean 생성!
var person = new Person(name, age, customBean);
return person;
}
@Bean
// "address"라는 이름의 Bean은 없으나, Primary가 있어 정상적으로 작동함.
public Person person4Parameters(String name, int age, Address address) {
var person = new Person(name, age, address);
return person;
}
@Bean
// 자동 주입 가능한 Bean이 2개 이상인 경우,
// Qualifier로 Bean을 지정할 수 있다.
public Person person5Qualifier(String name, int age, @Qualifier("address1qualifier") Address address) {
var person = new Person(name, age, address);
return person;
}
@Bean(name="customBean")
@Primary
public Address address() {
var address = new Address("Nowon-gu", "Seoul");
return address;
}
@Bean
@Qualifier("address1qualifier")
public Address address1() {
var address = new Address("Yongsan-gu", "Seoul");
return address;
}
}
itisyijy
25
Person[name=Cona, age=8, address=Address[firstLine=Gangnam-gu, city=Seoul]]
Person[name=itisyijy, age=25, address=Address[firstLine=Nowon-gu, city=Seoul]]
Person[name=itisyijy, age=25, address=Address[firstLine=Nowon-gu, city=Seoul]]
Address[firstLine=Nowon-gu, city=Seoul]
Address[firstLine=Nowon-gu, city=Seoul]
Person[name=Cona, age=8, address=Address[firstLine=Gangnam-gu, city=Seoul]]
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
helloWorldConfiguration
name
age
person
person2MethodCall
person3Parameters
person4Parameters
person5Qualifier
customBean
address1
Person[name=itisyijy, age=25, address=Address[firstLine=Yongsan-gu, city=Seoul]]
Process finished with exit code 0
package com.in28minutes.learn_spring_framework;
import com.in28minutes.learn_spring_framework.game.GameConfiguration;
import com.in28minutes.learn_spring_framework.game.GameRunner;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
// Iteration 3. Loose Coupling - Spring Level 1.
// Spring Beans
// Spring framework will manage objects and wiring
public class App03GamingSpringBeans {
public static void main(String[] args) {
var context = new AnnotationConfigApplicationContext(GameConfiguration.class);
var game1 = context.getBean("gameRunner"); // Object 타입을 리턴. 타입 캐스트 필요.
((GameRunner)game1) .run();
var game2 = context.getBean(GameRunner.class); // Bean의 클래스를 함께 전달.
game2 .run();
var game3 = context.getBean("gameRunner", GameRunner.class);
// Bean의 이름과 클래스를 함께 전달.
game3 .run();
}
}
package com.in28minutes.learn_spring_framework.game;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class GameConfiguration {
@Bean
public GamingConsole marioGame() {
return new MarioGame();
}
@Bean
public GameRunner gameRunner(GamingConsole marioGame) {
return new GameRunner(marioGame);
}
}
Running game: com.in28minutes.learn_spring_framework.game.MarioGame@4e3958e7
Jump
Go into a hole
Go back
Accelerate
Running game: com.in28minutes.learn_spring_framework.game.MarioGame@4e3958e7
Jump
Go into a hole
Go back
Accelerate
Running game: com.in28minutes.learn_spring_framework.game.MarioGame@4e3958e7
Jump
Go into a hole
Go back
Accelerate
Process finished with exit code 0
// Iteration 4. Loose Coupling - Spring Level 2.
// Spring Annotations
// Spring framework will create, manage & auto-wiring objects
Spring Bean과 Spring Bean의 수명주기 관리
Java Class, Configuration(e.g., HelloWorldConfiguration.java)을 인풋으로 전달. Spring Container는 Runtime System을 리턴.
종류
Java Bean: EJB(Enterprise Java Bean, 구식)에서 도입한 방법.
제한 사항
implements Serializable 선언POJO: 모든 Java 객체 통칭.
Spring Bean: Spring에서 관리하는 모든 객체는 Spring Bean!