본문 바로가기

IT/개발공부

Spring 처음부터 공부해보기 - 1 (프로젝트 생성, Bean, 어노테이션, Spring Container, Loose Coupling)

반응형

 

 

예전 프로젝트는 STS4를 이용하여 프로젝트를 생성했었지만,

이번엔 initializr로 프로젝트를 생성하고 eclipse를 사용할 예정이다.

 

 

 

 

1. 프로젝트 만들기

https://start.spring.io/

maven으로 선택해 준 뒤, springboot 버전과 이름들을 지정해 주고, CENERATE를 클릭하여 다운로드 해주면 된다.

다운로드 후, zip 압축을 출고 eclipse에서 폴더를 열어준다.

 

 

 

File > import > Existing Maven Projects

 

 

 

압축 푼 폴더를 지정해준다.

Finish를 클릭한다.

 

 

 

 

성공적으로 불러온 것을 확인할 수 있다.

 

 

 

 

AppGamingBasicJava 클래스를 하나 생성해준다.

 

 

 

 

package com.jm.learnspringframework;

public class AppGamingBasicJava {

	public static void main(String[] args) {

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

	}

}

marioGame과 gameRunner의 객체를 만들어 준 뒤, gameRunner의 run 메소드를 호출한다.

 

 

 

 

MarioGame과 GameRunner의 클래스를 생성해준다.

패키지는 AppGamingFrameworkApplication클래스가 있는 패키지에 .game을 추가한 패키지로 지정해준다.

 

 

 

package com.jm.learnspringframework.game;

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

GameRunner 생성자에서 MarioGame 인자를 받기 위해 위 코드와 같이 GameRunner 클래스를 수정해준다.

game인스턴스를 생성해 준 뒤, 생성자를 만들어 MarioGame을 받을 수 있도록 하였다.

 

 

 

 

 

package com.jm.learnspringframework.game;

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

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

그리고 run 메소드를 호출했기 때문에 run메소드도 만들어 준다.

 

AppGamingBasicJava 클래스에서 코드를 실행시키면 

Running game : com.jm.learnspringframework.game.MarioGame@28a418fc

이렇게 출력되는 것을 확인할 수 있다.

 

 

 

 

package com.jm.learnspringframework.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");
	}
}

위, 아래, 왼쪽, 오른쪽 4가지 동작을 수행할 수 있도록 4개의 메소드를 만들어 주었다.

 

 

 

 

package com.jm.learnspringframework.game;

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();
	}
}

GameRunner에서 4개의 메소드를 호출하였고,

AppGamingBasicJava 클래스에서 해당 메소드가 잘 실행되는지 확인하였다.

Running game : com.jm.learnspringframework.game.MarioGame@28a418fc

jump

Go into a hole

Go back

Accelerate

 

 

 

 

 

위의 코드대로 하면 여러 게임들을 쉽게 바꿀 수 없기 때문에 해당 그림처럼 인터페이스를 추가하여 수정해주어야 한다.

(Tightly Coupled Java Code -> Loose Coupling(Interface))

 

 

 

 

 

게임 클래스들이 있는 패키지에 GamingConsole 인터페이스를 생성해준다.

 

 

 

 

그리고 SuperContraGame클래스도 생성해준다.

 

 

 

 

GameRunner.java

package com.jm.learnspringframework.game;

public class GameRunner {
	private GamingConsole 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();
	}
}

 

GamingConsole.java

package com.jm.learnspringframework.game;

public interface GamingConsole {
	void up();
	void down();
	void left();
	void right();
}

 

MarioGame.java

package com.jm.learnspringframework.game;

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");
	}
}

 

 

SuperContraGame.java

package com.jm.learnspringframework.game;

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("shout a bullet");
	}
}

 

 

위의 코드들 처럼 수정하게 되면,

package com.jm.learnspringframework;

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

public class AppGamingBasicJava {

	public static void main(String[] args) {

		
		var game = new MarioGame();
		//var game = new SuperContraGame();
		var gameRunner = new GameRunner(game);
		gameRunner.run();

	}

}

//실행결과
Running game : com.jm.learnspringframework.game.MarioGame@5305068a
jump
Go into a hole
Go back
Accelerate
package com.jm.learnspringframework;

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

public class AppGamingBasicJava {

	public static void main(String[] args) {

		
		//var game = new MarioGame();
		var game = new SuperContraGame();
		var gameRunner = new GameRunner(game);
		gameRunner.run();

	}

}

//실행결과
Running game : com.jm.learnspringframework.game.SuperContraGame@5305068a
up
sit down
Go back
shout a bullet

AppGamingBaseJava에서 주석처리를 통해 game을 MarioGame으로 바꾸거나, SuperContraGame으로 바꾸어도 정상적으로 작동된다.

 

 

 

 

이번에는 PacMan을 추가해준다.

PacmanGame 클래스를 추가해준 뒤,

 

 

 

package com.jm.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("left");
	}
	
	public void right() {
		System.out.println("right");
	}
}

다른 게임들과 같은 코드를 넣어준다.

 

 

 

 

package com.jm.learnspringframework;

import com.jm.learnspringframework.game.GameRunner;
import com.jm.learnspringframework.game.MarioGame;
import com.jm.learnspringframework.game.SuperContraGame;
import com.jm.learnspringframework.game.PacmanGame;


public class AppGamingBasicJava {

	public static void main(String[] args) {

		
		//var game = new MarioGame();
		//var game = new SuperContraGame();
		
		var game = new PacmanGame();
		var gameRunner = new GameRunner(game);
		gameRunner.run();

	}

}

//실행결과
Running game : com.jm.learnspringframework.game.PacmanGame@5305068a
up
down
left
right

AppGamingBasicJava에서 이제 Pacman을 실행시키기 위해 다른 게임들은 주석처리해주고 pacman 객체를 만들어준다.

그러면 정상적으로 작동되는 것을 확인할 수 있다.

(import 까먹지 말기)

 

 

 

이번에는 Bean으로 spring이 관리할 수 있도록 해볼 것이다.

 

먼저, AppGamingBasicJava 클래스의 이름을 사진과 같이 변경해준다.

 

 

 

 

 

그리고 처음부터 있었던 LearnSpringFrameworkApplication.java 클래스를 삭제해준다.

 

 

 

 

 

 

App01GamingBasicJava 클래스를 복사하여 App02HelloWorldSpring 클래스도 생성해준다.

 

 

 

 

AnnotationConfig를 고를 땐, ApplicationContext를 골라준다.

 

 

 

 

 

 

HelloWorldConfiguration.java

package com.jm.learnspringframework;

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

//JDK16에서 새로생긴 기능(record)
//getter, setter와 같은 것들을 안만들어도 됨 (자동으로 생성됨)
record Person(String name, int age) {};
record Address(String Contry, String City) {};

@Configuration
public class HelloWorldConfiguration {
	
	@Bean
	public String name() {
		return "Ranga";
	}
	
	@Bean
	public int age() {
		return 21;
	}
	
	@Bean
	public Person person() {
		return new Person("Jimin", 20);
	}
	
	@Bean(name = "address2")
	public Address address() {
		return new Address("Korea", "Incheon");
	}
}

 

 

 

 

 

App02HelloWorldSpring.java

package com.jm.learnspringframework;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class App02HelloWorldSpring {

	public static void main(String[] args) {
		// 1: Spring Context 생성
		var context = new AnnotationConfigApplicationContext(HelloWorldConfiguration.class);
		
		// 2: Spring이 관리하는 bean 구성 -
		// HelloWorldConfiguration - @Configuration
		// name - @Bean
		
		// 3: Bean 호출
		System.out.println(context.getBean("name")); 	//생성한 Bean 이름이 name이다.
		System.out.println(context.getBean("age"));
		System.out.println(context.getBean("person"));
		
		System.out.println(context.getBean("address2"));
		System.out.println(context.getBean(Address.class));
	}

}

//실행결과
Ranga
21
Person[name=Jimin, age=20]
Address[Contry=Korea, City=Incheon]
Address[Contry=Korea, City=Incheon]

 

 

 

 

이번에는 호출을 통해 person2 객체를 만들어 보았다.

package com.jm.learnspringframework;

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

//JDK16에서 새로생긴 기능(record)
//getter, setter와 같은 것들을 안만들어도 됨 (자동으로 생성됨)
record Person(String name, int age, Address address) {};
record Address(String Contry, String City) {};

@Configuration
public class HelloWorldConfiguration {
	
	@Bean
	public String name() {
		return "Ranga";
	}
	
	@Bean
	public int age() {
		return 21;
	}
	
	@Bean
	public Person person() {
		return new Person("Jimin", 20, new Address("Korea", "seoul"));
	}
	
	@Bean
	public Person person2() {
		return new Person(name(), age(), address());
	}
	
	@Bean(name = "address2")
	public Address address() {
		return new Address("Korea", "Incheon");
	}
}

//실행 결과
Ranga
21
Person[name=Jimin, age=20, address=Address[Contry=Korea, City=seoul]]
Address[Contry=Korea, City=Incheon]
Address[Contry=Korea, City=Incheon]
Person[name=Ranga, age=21, address=Address[Contry=Korea, City=Incheon]]

관리되고 있는 기존 Bean을 이용하여 새로운 Bean을 만들 수 있는 것이다.

 

 

이미 존재하는 Bean을 호출하는 것이 아닌 매개변수를 이용하는 방법도 있다.

package com.jm.learnspringframework;

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

//JDK16에서 새로생긴 기능(record)
//getter, setter와 같은 것들을 안만들어도 됨 (자동으로 생성됨)
record Person(String name, int age, Address address) {};
record Address(String Contry, String City) {};

@Configuration
public class HelloWorldConfiguration {
	
	@Bean
	public String name() {
		return "Ranga";
	}
	
	@Bean
	public int age() {
		return 21;
	}
	
	@Bean
	public Person person() {
		return new Person("Jimin", 20, new Address("Korea", "seoul"));
	}
	
	@Bean
	public Person person2MethodCall() {
		return new Person(name(), age(), address());
	}
	
	@Bean
	public Person person3Parameters(String name, int age, Address address2) {//name, age, address2
		return new Person(name, age, address2);
	}
	
	@Bean(name = "address2")
	public Address address() {
		return new Address("Korea", "Incheon");
	}
}

//실행결과
Ranga
21
Person[name=Jimin, age=20, address=Address[Contry=Korea, City=seoul]]
Address[Contry=Korea, City=Incheon]
Address[Contry=Korea, City=Incheon]
Person[name=Ranga, age=21, address=Address[Contry=Korea, City=Incheon]]
Person[name=Ranga, age=21, address=Address[Contry=Korea, City=Incheon]]

 

 

 

 

JVM 내부에는 구성한 모든 Bean을 관리하는 스프링 컨텍스트가 있다.

 

스프링 컨테이너 : spring Bean과 그 lifecycle을 관리하는 컨테이너이다.

2가지 종류가 있음

  • Bean Factory :  기본 spring container
  • Application Context : 엔터프라이즈 특정 기능을 가진 고급 spring container (위의 코드에서 사용함)
    • web application 제작, 국제화 기능, spring AOP 지향 프로그래밍을 위함

 

 

bean의 3가지 종류

  • Pojo : Plain Old Java Object로 오래된 평범한 java 객체이다. (null:0)
  • spring Bean : IOC 컨테이너에서 관리하는 Bean은 모두 Spring Bean이다.
  • Java Bean : 클래스가 패키지화 되어야 한다. 등 여러 규약이 있다.

 

 

 

getBeanDefinitionNames를 이용해서 관리되고 있는 Bean들을 출력할 수 있다.

package com.jm.learnspringframework;

import java.util.Arrays;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class App02HelloWorldSpring {

	public static void main(String[] args) {
		// 1: Spring Context 생성
		var context = new AnnotationConfigApplicationContext(HelloWorldConfiguration.class);
		
		// 2: Spring이 관리하는 bean 구성 -
		// HelloWorldConfiguration - @Configuration
		// name - @Bean
		
		// 3: Bean 호출
		System.out.println(context.getBean("name")); 	//생성한 Bean 이름이 name이다.
		System.out.println(context.getBean("age"));
		System.out.println(context.getBean("person"));
		
		System.out.println(context.getBean("address2"));
		System.out.println(context.getBean(Address.class));
		
		System.out.println(context.getBean("person2MethodCall"));
		System.out.println(context.getBean("person3Parameters"));
		
		System.out.println("-------------------------------");
		Arrays.stream(context.getBeanDefinitionNames()).forEach(System.out::println);
	}

}

//실행 결과
Ranga
21
Person[name=Jimin, age=20, address=Address[Contry=Korea, City=seoul]]
Address[Contry=Korea, City=Incheon]
Address[Contry=Korea, City=Incheon]
Person[name=Ranga, age=21, address=Address[Contry=Korea, City=Incheon]]
Person[name=Ranga, age=21, address=Address[Contry=Korea, City=Incheon]]
-------------------------------
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
address2

 

 

 

같은 Bean이 여러개 일때는 예외가 생기게 된다. 

여러 개의 Bean 중 하나를 우선순위에 두는 것이 필요하다.

@Bean 어노테이션 밑에 @Primary를 추가해주면 된다.

 

또, @Bean 어노테이션 밑에 @Qualifier 어노테이션을 추가할 수도 있는데,

같은 타입의 클래스를 구별할 수 있도록 해주는 것이다.

 

 

 

이제 지금까지 했던 HelloWorldConfiguration.java와 App02HelloWorldSpring.java 파일은 새로운 .helloworld 패키지로 이동시켜준다.

 

 

 

 

 

현재 context를 보면 이러한 warning이 뜨는 것을 볼 수 있다.

 

 

 

 

package com.jm.learnspringframework.helloworld;

import java.util.Arrays;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class App02HelloWorldSpring {

	public static void main(String[] args) {
		// 1: Spring Context 생성
		try(var context = new AnnotationConfigApplicationContext(HelloWorldConfiguration.class)){
			// 2: Spring이 관리하는 bean 구성 -
			// HelloWorldConfiguration - @Configuration
			// name - @Bean
			
			// 3: Bean 호출
			System.out.println(context.getBean("name")); 	//생성한 Bean 이름이 name이다.
			System.out.println(context.getBean("age"));
			System.out.println(context.getBean("person"));
			
			System.out.println(context.getBean("address2"));
			System.out.println(context.getBean(Address.class));
			
			System.out.println(context.getBean("person2MethodCall"));
			System.out.println(context.getBean("person3Parameters"));
			
			System.out.println("-------------------------------");
			Arrays.stream(context.getBeanDefinitionNames()).forEach(System.out::println);
		}
	}
}

이렇게 try문으로 고쳐주면 warning이 없어진다.

 

 

 

이제 게임을 만들던 곳에도 Bean을 생성해 줄 것이다.

App01GamingBasicJava.java 파일을 복붙 해주고 이름은 App02GamingSpringBeans로 해준다.

그리고 같은 패키지에 GamingConfiguration 클래스도 하나 생성해준다.

 

 

 

 

 

GamingConfiguration.java

package com.jm.learnspringframework;

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

import com.jm.learnspringframework.game.GameRunner;
import com.jm.learnspringframework.game.GamingConsole;
import com.jm.learnspringframework.game.PacmanGame;

@Configuration
public class GamingConfiguration {
	@Bean
	public GamingConsole game() {
		var game = new PacmanGame();	//PacmanGame만 생성해줌.
		return game;
	}
	
	@Bean
	public GameRunner gameRunner(GamingConsole game) {
		var gameRunner = new GameRunner(game);			//Pacman 게임을 가져와서 GameRunner를 생성해줌
		return gameRunner;
	}
}

 

 

 

App03GammingSpringBeans.java

package com.jm.learnspringframework;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import com.jm.learnspringframework.game.GameRunner;
import com.jm.learnspringframework.game.GamingConsole;


public class App03GamingSpringBeans {

	public static void main(String[] args) {
		try(var context = new AnnotationConfigApplicationContext(GamingConfiguration.class)){
			context.getBean(GamingConsole.class).up();	//생성된 Pacman의 up메소드 호출
			context.getBean(GameRunner.class).run();	//pacman을 넣어준 gamerunner의 run 메소드를 호출
		}
	}

}

// 실행결과
up
Running game : com.jm.learnspringframework.game.PacmanGame@10959ece
up
down
left
right
21:25:19.499 [main] DEBUG org.springframework.context.annotation.AnnotationConfigApplicationContext -- Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@66d33a, started on Thu Jun 15 21:25:19 KST 2023

 

 

 

 

 

 

 

(다음 게시글은 코드를 더 쉽게하고 더 많은 어노테이션을 공부할 예정..)

 

 

반응형