ㅁ IoC(Inversion of Control)
- 제어의 역전
- 개발자가 프로그램을 제어하지 않고 Framework가 프로그램을 제어하는 것을 의미한다.
- 객체 생성 및 생명주기 관리, 의존관계 설정 등을 개발자가 아닌 Framework가 직접 한다.
개발에 필요한 객체들을 스프링에 등록해두고 ~~ 가져다 쓴다.
- 개발자가 객체를 생성하지 않고 스프링이 가지고 있다가 주입받아서 사용한다.
- 개발자가 객체의 생성, 초기화, 생명주기 등을 직접 관리하지 않고, 스프링 프레임워크가 대신 관리하도록 하는 개념입니다. 즉, 제어 흐름의 주도권을 개발자에서 프레임워크로 넘기는 것 을 의미합니다.
ㅁ IoC 컨테이너
- 개발에 필요한 객체들, 의존성 ~
- 위의 내용들을 전반적으로 관리하는 컨테이너로
개발에 필요한 객체들을 스프링이 관리하게끔 하기 위해서는
해당 객체들을 먼저 빈(Bean)으로 등록해야 된다.
- 개발에 필요한 객체들을 빈(Bean)이라고 한다.
이때 등록된 빈(Bean == 객체)들을 가지고 있는 컨테이너다..
- Ioc 컨테이너 == 스프링 컨테이너 == 빈 컨테이너
※ IoC 컨테이너에 Bean을 등록하는 방법
(1) xml 방식
- Spring Bean Configuration File(xml)에 <bean> 태그로 등록하는 방법.
(2) java 방식
- @Configuration 클래스에서 @Bean을 이용해서 등록하는 방법.
※ 등록된 Bean을 사용하는 방법 (컨테이너로부터 가져오는 방법 - 원론적인 방법)
(1) 빈 등록 구문이 쓰여져있는 xml 파일 또는 java 파일을 읽어들이면서 컨테이너 객체 생성.
(2) 해당 컨테이너로부터 getBean 메소드로 객체를 가져옴. <- 이거 자체가 스프링에서의 DI(의존성 주입) 특징임.
- 원론적인 방법이었고 앞으로 배울 di 방식을 이용하면 조금 더 쉽게 객체를 가져올 수 있다.
- DI(Dependency Injection, 의존성 주입)는 스프링의 핵심 개념 중 하나로, 객체 간의 의존 관계를 외부에서 주입 하는 방식입니다. 간단히 말하면, 클래스가 사용할 객체(의존성)를 직접 생성하는 대신, 스프링이 그 객체를 대신 생성하고 주입 해주는 구조를 의미합니다.
- 일반적인 코드에서는 클래스 내부에서 필요한 객체를 new로 직접 생성합니다. 하지만 DI를 사용하면, 클래스가 직접 객체를 생성하지 않고, 외부에서 필요한 객체를 주입받습니다 . 즉, 객체의 생성과 사용이 분리됩니다.
- 스프링 컨테이너는 애플리케이션이 필요로 하는 모든 객체(빈)를 미리 생성 하고, 이 객체들을 관리합니다. 개발자가 직접 new 키워드로 객체를 생성하지 않고, 필요할 때 스프링 컨테이너에서 가져오는 방식 이 DI의 핵심입니다.
- getBean() 메소드를 통해 스프링 컨테이너로부터 객체를 가져오는 과정 자체가 DI의 방식 이라는 의미이다.
- IoC (Inversion of Control, 제어의 역전)와 DI (Dependency Injection, 의존성 주입)는 밀접하게 관련이 있지만, 서로 다른 개념입니다. 쉽게 말해, DI는 IoC를 구현하는 한 가지 방법 입니다.
- DI는 IoC를 구체화하는 방법 중 하나로, 필요한 객체(의존성)를 외부에서 주입받는 방식 입니다. 즉, 클래스가 필요한 객체를 스스로 생성하지 않고, 외부에서 스프링 컨테이너가 주입해줍니다.
====================================================================
※ IoC 컨테이너에 Bean을 등록하는 방법
(1) xml 방식
- Spring Bean Configuration File(xml)에 <bean> 태그로 등록하는 방법.
ㅁ 패키지 생성
- com.br.spring.ioc
- com.br.spring.ioc.xml (빈 등록을 xml방식으로 해볼 예정이다)
- com.br.spring.di
ㅁ 클래스 생성
- com.br.spring.ioc.xml에 Calculator 클래스 생성
- com.br.spring.ioc.xml에 Student 클래스 생성
- 이렇게 만든 클래스를 스프링에서 관리할 수 있게 빈으로 등록할 예정이다.
- 테스트는 테스트할 때 쓰는 (소스)폴더라 당장 쓸 일은 없다.
- webapp이 배포되는 폴더다. webapp의 resources는 위에 있는 resources랑 다르다. 얘는 일반 폴더고 정적 자원들을 보관하는 폴더다.
ㅁ com.br.spring.ioc.xml.Calculator
package com.br.spring.ioc.xml;
public class Calculator {
public void plus ( int x , int y ) {
System . out . printf ( "%d + %d = %d \n " , x, y, x+y);
}
public void minus ( int x , int y ) {
System . out . printf ( "%d - %d = %d \n " , x, y, x-y);
}
}
ㅁ com.br.spring.ioc.xml.Student
package com.br.spring.ioc.xml;
public class Student {
private String name ;
private String academy ;
private String classColor ;
private Calculator calc ;
public Student () {
super ();
}
public Student ( String name , String academy , String classColor , Calculator calc ) {
super ();
this . name = name;
this . academy = academy;
this . classColor = classColor;
this . calc = calc;
}
public String getName () {
return name;
}
public void setName ( String name ) {
this . name = name;
}
public String getAcademy () {
return academy;
}
public void setAcademy ( String academy ) {
this . academy = academy;
}
public String getClassColor () {
return classColor;
}
public void setClassColor ( String classColor ) {
this . classColor = classColor;
}
public Calculator getCalc () {
return calc;
}
public void setCalc ( Calculator calc ) {
this . calc = calc;
}
@ Override
public String toString () {
return "Student [name=" + name + ", academy=" + academy + ", classColor=" + classColor + ", calc=" + calc + "]" ;
}
}
- Calculator형 객체를 담을 수 있는 참조변수 calc가 있다.
ㅁ 깃에 올리기
-
- "classes/" 추가
- file - new repository
- readme 파일 체크.
- 지금 로컬저장소로만 등록됐고 올라가진 않았다.
- history 탭에 가면 현재 커밋되어 있다.
- 커밋된게 엄청 많으면 gitignore가 잘 있는지 확인해야 한다.
gitignore가 뒤늦게 추가되었다면 undo로 initial commit을 취소하고 다시 커밋한다.
====================================================================================
[2024.10.15]
ㅁ sts도 이클립스다. 스프링 개발 환경이 있는 이클립스.
ㅁ 스프링의 특징 IOC, DI를 알아보기 위한 프로젝트를 만들었었다.
- db연동도 안하고 웹개발도 안할거다. 그냥 특징만 알아보기.
- 그 전에는 필요한 라이브러릴를 lib에 넣었는데 이제 그럴 필요가 없다.
maven이란 도구를 이용해서 pom.xml을 이용해서 버전이나 라이브러리 관리를 한다.
- 스프링은 기본적으로 메이븐을 쓴다.
- 메이븐은 프로젝트 관리 도구로 얘도 프레임워크다.
ㅁ 9시 14분. 20초. 사설저장소 얘기.
ㅁ 9시 19분. 1분. 폴더들 설명.
ㅁ 베이스패키지로 com.br.spring을 지정했더니 패키지가 만들어져 있다.
ㅁ 9시 22분. 30초. ioc 설명. 객체 직접 생성 안 한다.
ㅁ 앞으로 스튜던트, 칼큘레이터 객체가 필요하다면 내가 생성하지 않고, 스프링이 가지고 있따가 나한테 넘겨주는 방식으로 한다. 9시 25분 10초.
두 클래스를 스프링이 관리할수있게끔 하려면 빈으로 등록을 해야함.
- 그럼 이 두 클래스를 빈으로 등록하는 xml 파일을 만들어야 한다.
ㅁ src/main/resources 안에 일반 폴더 "xml"을 만든다.
- 일반적인 xml파일이면 안된다. 9시27분 10초. 스프링 빈 어쩌구 파일.
- xml폴더 우클릭 - spring bean configuration file
- 파일명은 중요하지 않다. finish.
- 9시29분. 10초. xml파일은 맞긴한데 본적없는 태그 ~
ㅁ Spring Bean Configuration File
- Bean(개발에 사용할 객체)을 등록할 수 있는 xml 파일.
- xml 파일에 등록된 Bean은 IoC 컨테이너에 보관된다. 그러면 이제부터 스프링이 이 객체를 관리한다.
<bean class="등록시키고자하는 클래스명" id="빈의 고유한 이름"> </bean>
- 9시31분. 10초. 내부적으로 객체가 생성되고, 그 생성된 객체를 스프링이 관리하게 된다.
혹시 객체 내에 필드듣ㄹ이 ㅣㅇㅆ을 수 있다.
필드에 값이 담긴 채로 객
- 필드에 값을 주입한 채로 생성할 수 있다.
해당 객체 생성 후 필드에 값을 넣고자 할 경우 setter라는 방식으로 값을 주입하거나 또는 매개변수 생성자를 이용해서 주입할 수 있다.
- 9시 33분. 40초. 스프링이 알아서 어쩌구~ calculator 객체가 필요 메소드를 사용하려고 어쩌꾸~
(1) <bean>
- 빈을 생성하는 태그
- 내부적으로 객체가 생성돼서 빈 컨테이너에 등록된다.
- class 속성 : 빈으로 등록하고자 하는 클래스명 작성(풀 클래스명)
- id 속성 : 해당 빈의 고유한 이름 작성.
ㅁ setter Injection 예시
(1) <property>
- 생성된 객체의 필드에 setter 메소드로 값을 주입할 때 사용되는 태그
- name 속성 : 값을 주입하고자 하는 필드명 작성.
- value 속성 : 담고자하는 값 작성.
- ref 속성 : 담고자하는 값이 레퍼런스 타입일 경우, 레퍼런스 타입의 값 작성.
- 레퍼런스 타입의 값을 주입할 때는 value 속성이 아닌 ref 속성을 사용한다.
ㅁ setter Injection 예시2 (property 태그를 사용하지 않는 방법)
- 하단에 보면 source탭이 활성화되어 있다.
Namespaces 탭을 가면 beans만 체크되어 있다.
이게 체크되어 있어서 beans라는 태그가 어쩌구. 10시2분 10초.
- p를 체크한다. 어쩌구
- 스키마가 생겼다. 어쩌구.
- 10시9분 10초. 매개변수 4개짜리 생성자니까 반드시 4개의 값을 제시해야 한다.
ㅁ constructor Injection 예시
(1) <constructor-arg>
- 생성된 객체의 필드에 매개변수 생성자를 이용하여 값을 주입할 때 사용되는 태그
- value : 담고자하는 값 작성.
- ref : 레퍼런스 타입의 값 작성.
ㅁ 최종 appCtx.xml
<? xml version = "1.0" encoding = "UTF-8" ?>
< bean class = "com.br.spring.ioc.xml.Calculator" id = "calculator" />
<!--
[내부적으로 실행되는 코드]
Calculator calculator = new Calculator();
-->
<!-- setter Injection 예시1 -->
< bean class = "com.br.spring.ioc.xml.Student" id = "student1" >
< property name = "name" value = "카리나" ></ property >
< property name = "academy" value = "sm아카데미" ></ property >
< property name = "classColor" value = "빨강색" />
< property name = "calc" ref = "calculator" />
</ bean >
<!--
[내부적으로 실행되는 코드]
Student student1 = new Student();
student1.setName("카리나");
student1.setAcademy("sm아카데미");
student1.setClassColor("빨강색");
student1.setCalc(calculator);
-->
<!-- setter Injection 예시2 (property 태그를 사용하지 않는 방법) -->
<!-- Namespace로 p 추가하면 property 태그 대신 p:필드명 속성으로 값 주입 가능 -->
< bean class = "com.br.spring.ioc.xml.Student" id = "student2"
p:name = "장원영"
p:academy = "스타쉽아카데미"
p:classColor = "파랑색"
p:calc-ref = "calculator" />
<!-- constructor Injection 예시 -->
< bean class = "com.br.spring.ioc.xml.Student" id = "student3" >
< constructor-arg value = "권나라" />
< constructor-arg value = "고스트아카데미" />
< constructor-arg value = "초록색" />
< constructor-arg ref = "calculator" />
</ bean >
<!--
[내부적으로 실행되는 코드]
Student student3 = new Student("권나라", "고스트아카데미", "초록색", calculator);
-->
</ beans >
- 4개의 객체를 bean으로 등록해놨다. 10시11분 10초. 직접 new로 생성하지 않는다.
ㅁ com.br.spring.ioc.xml에 SpringMain이라는 클래스를 만든다.
package com.br.spring.ioc.xml;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;
public class SpringMain {
public static void main ( String [] args ) {
// 스프링 사용 전(개발자가 필요한 객체를 직접 생성해서 사용)
// Calculator calc = new Calculator();
// 스프링 사용 후(프레임워크가 미리 생성해둔 빈을 가져와 사용 - 내가 직접 생성하지 않음)
// 컨테이너에 저장된 빈을 가져다 쓸 때 사용되는 클래스. 이게 곧 컨테이너 객체다.
// GenericXmlApplicationContext
AbstractApplicationContext ctx = new GenericXmlApplicationContext ( "classpath:xml/appCtx.xml" );
// ctx가 곧 컨테이너다. 이 안에 빈(객체)들이 담겨있다.
// Calculator calc = (Calculator) ctx.getBean("calculator"); 대신 아래도 가능.
Calculator calc = ctx . getBean ( "calculator" , Calculator . class );
calc . plus ( 2 , 3 );
calc . minus ( 4 , 2 );
Student stu1 = ctx . getBean ( "student1" , Student . class );
Student stu2 = ctx . getBean ( "student2" , Student . class );
Student stu3 = ctx . getBean ( "student3" , Student . class );
}
}
==================================================================================
ㅁ com.br.spring.ioc.xml에 StudentDao 클래스를 만든다.
package com.br.spring.ioc.xml;
public class StudentDao {
public void selectStudentList () {
System . out . println ( "학생전체조회용 쿼리 실행" );
}
public void insertStudent () {
System . out . println ( "학생등록용 쿼리 실행" );
}
}
ㅁ SpringMain 클래스 추가 작성
package com.br.spring.ioc.xml;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;
public class SpringMain {
public static void main ( String [] args ) {
// 스프링 사용 전(개발자가 필요한 객체를 직접 생성해서 사용)
// Calculator calc = new Calculator();
// 스프링 사용 후(프레임워크가 미리 생성해둔 빈을 가져와 사용 - 내가 직접 생성하지 않음)
// 컨테이너에 저장된 빈을 가져다 쓸 때 사용되는 클래스. 이게 곧 컨테이너 객체다.
// GenericXmlApplicationContext
AbstractApplicationContext ctx = new GenericXmlApplicationContext ( "classpath:xml/appCtx.xml" );
// ctx가 곧 컨테이너다. 이 안에 빈(객체)들이 담겨있다.
// Calculator calc = (Calculator) ctx.getBean("calculator"); 대신 아래도 가능.
Calculator calc = ctx . getBean ( "calculator" , Calculator . class );
calc . plus ( 2 , 3 );
calc . minus ( 4 , 2 );
Student stu1 = ctx . getBean ( "student1" , Student . class );
Student stu2 = ctx . getBean ( "student2" , Student . class );
Student stu3 = ctx . getBean ( "student3" , Student . class );
System . out . println ( "======================================================" );
// 스프링 사용 전 Service에서 Dao측 메소드 호출을 위해 직접 Dao 객체를 new로 생성해서 호출함.
// 서비스 단위마다 매번 dao 객체가 생성되고 소멸되기를 반복함.
// 매 요청마다 새로운 dao 객체가 생성되고 소멸된다. 만번, 10만번씩 반복되면 메모리 낭비가 심하다.
// 매번 주소값이 다르다. 메모리 사용이 빈번하다.
// 자주 사용되는 객체는 메모리상에 한번만 생성해두고 재사용하는걸 권장 (싱글톤 패턴)
StudentDao dao1 = new StudentDao ();
dao1 . selectStudentList ();
System . out . println (dao1);
StudentDao dao2 = new StudentDao ();
dao2 . insertStudent ();
System . out . println (dao2);
}
}
- 스프링에서는 이러한 문제를 해결하기 위해 IoC 컨테이너 를 통해 객체를 한 번 생성하고 , 필요할 때마다 재사용 합니다. 이를 통해 메모리 사용을 최적화하고, 자원 낭비를 방지할 수 있습니다. DAO 객체를 필요할 때마다 매번 생성하는 대신, 싱글톤(Singleton)으로 관리할 수 있게 됩니다.
ㅁ appctx.xml에 한 줄을 추가한다.
< bean class = "com.br.spring.ioc.xml.StudentDao" id = "studentDao" ></ bean >
- 여기에 scope 속성을 쓸 수 있다. 생략시 기본값이 scope="singleton"이다.
그래서 매번 객체의 주소값이 동일하다.
- 매 요청마다 새로운 객체를 생성시키고 싶다면 scope="prototype"을 주면 되는데 쓸 일은 없을 것.
ㅁ SpringMain 클래스 추가 작성
package com.br.spring.ioc.xml;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;
public class SpringMain {
public static void main ( String [] args ) {
// 스프링 사용 전(개발자가 필요한 객체를 직접 생성해서 사용)
// Calculator calc = new Calculator();
// 스프링 사용 후(프레임워크가 미리 생성해둔 빈을 가져와 사용 - 내가 직접 생성하지 않음)
// 컨테이너에 저장된 빈을 가져다 쓸 때 사용되는 클래스. 이게 곧 컨테이너 객체다.
// GenericXmlApplicationContext
AbstractApplicationContext ctx = new GenericXmlApplicationContext ( "classpath:xml/appCtx.xml" );
// ctx가 곧 컨테이너다. 이 안에 빈(객체)들이 담겨있다.
// Calculator calc = (Calculator) ctx.getBean("calculator"); 대신 아래도 가능.
Calculator calc = ctx . getBean ( "calculator" , Calculator . class );
calc . plus ( 2 , 3 );
calc . minus ( 4 , 2 );
Student stu1 = ctx . getBean ( "student1" , Student . class );
Student stu2 = ctx . getBean ( "student2" , Student . class );
Student stu3 = ctx . getBean ( "student3" , Student . class );
System . out . println ( "======================================================" );
// 스프링 사용 전 - Service에서 Dao측 메소드 호출을 위해 직접 Dao 객체를 new로 생성해서 호출함.
// 서비스 단위마다 매번 dao 객체가 생성되고 소멸되기를 반복함.
// 매 요청마다 새로운 dao 객체가 생성되고 소멸된다. 만번, 10만번씩 반복되면 메모리 낭비가 심하다.
// 매번 주소값이 다르다. 메모리 사용이 빈번하다.
// 자주 사용되는 객체는 메모리상에 한번만 생성해두고 재사용하는걸 권장 (싱글톤 패턴)
/*
StudentDao dao1 = new StudentDao();
dao1.selectStudentList();
System.out.println(dao1);
StudentDao dao2 = new StudentDao();
dao2.insertStudent();
System.out.println(dao2);
*/
// 스프링 사용 후 - Dao 클래스를 스프링이 관리하도록 빈으로 등록하고 필요할 때마다 불러서 쓰면 됨.
StudentDao dao1 = ctx . getBean ( "studentDao" , StudentDao . class );
dao1 . selectStudentList ();
System . out . println (dao1);
StudentDao dao2 = ctx . getBean ( "studentDao" , StudentDao . class );
dao2 . insertStudent ();
System . out . println (dao2);
}
}
- 주소값이 같다. 한번 생성된 객체를 재사용하고 있다. 메모리를 덜 쓴다.
- 스프링이기 때문에 가능한 특징이다.
=================================================================================
※ IoC 컨테이너에 Bean을 등록하는 방법
(2) java 방식
- @Configuration 클래스에서 @Bean을 이용해서 등록하는 방법.
ㅁ 패키지 생성
- com.br.spring.ioc.java
ㅁ 클래스 생성
- com.br.spring.ioc.java에 Music 클래스 생성
- com.br.spring.ioc.java에 Singer 클래스 생성
package com.br.spring.ioc.java;
public class Music {
private String title ;
private String genre ;
public Music () {}
public Music ( String title , String genre ) {
super ();
this . title = title;
this . genre = genre;
}
public String getTitle () {
return title;
}
public void setTitle ( String title ) {
this . title = title;
}
public String getGenre () {
return genre;
}
public void setGenre ( String genre ) {
this . genre = genre;
}
@ Override
public String toString () {
return "Music [title=" + title + ", genre=" + genre + "]" ;
}
}
package com.br.spring.ioc.java;
public class Singer {
private String name ;
private Music music ;
public Singer () { }
public Singer ( String name , Music music ) {
super ();
this . name = name;
this . music = music;
}
public String getName () {
return name;
}
public void setName ( String name ) {
this . name = name;
}
public Music getMusic () {
return music;
}
public void setMusic ( Music music ) {
this . music = music;
}
@ Override
public String toString () {
return "Singer [name=" + name + ", music=" + music + "]" ;
}
}
- 이 두 클래스도 개발에 필요하면 빈에 등록하고 스프링으로 가져와서 쓸 수 있다.
ㅁ com.br.spring.ioc.java에 SpringBeanConfig 클래스 생성
package com.br.spring.ioc.java;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ Configuration
public class SpringBeanConfig {
// setter Injection 예시1
@ Bean
public Music music1 () {
Music m = new Music ();
m . setTitle ( "Love wins all" );
m . setGenre ( "발라드" );
return m;
}
/*
[xml방식]
<bean class="com.br.spring.ioc.java.Music" id="music1">
<property name="title" value="Love wins all" ></property>
<property name="genre" value="발라드" />
</bean>
*/
// setter Injection 예시2
@ Bean
public Singer singer1 () {
Singer s = new Singer ();
s . setName ( "아이유" );
s . setMusic ( music1 ());
return s;
}
// constructor Injection 예시
/*
@Bean
public Music music2() {
return new Music("EASY", "K-POP");
}
*/
// 메소드명은 마음대로 짓고 빈의 이름을 따로 작성하는 방법 (name속성으로 별도로 빈 이름 지정 가능)
@ Bean (name= "music2" )
public Music abcd () {
return new Music ( "EASY" , "K-POP" );
}
/*
@Bean
public Singer singer2() {
return new Singer("르세라핌", music2());
}
*/
// 메소드명은 마음대로 짓고 빈의 이름을 따로 작성하는 방법 (name속성으로 별도로 빈 이름 지정 가능)
@ Bean
public Singer qwer () {
return new Singer ( "르세라핌" , abcd ());
}
}
- 일반클래스를 빈을 등록시키는 클래스로 하고 싶다면 어노테이션을 붙여줘야한다.
ㅁ 자바방식은 어노테이션을 붙여서 빈을 등록하는 방식이다.
ㅁ @Configuration
- Spring Bean Configuration File의 역할을 대신해줄 Java 클래스에 부여하는 annotation
ㅁ @Bean
- Java에서 Bean을 등록할 때 사용하는 어노테이션
- @Configuration 클래스 내에 메소드 하나당 빈 하나를 생성
- 메소드명이 등록할 빈의 이름으로 잡힌다. xml 방식에서 bean 태그에서의 id 속성과 동일한 기능이다.
- 반환형은 등록할 빈의 타입이다. xml 방식에서 bean 태그에서의 class 속성과 동일한 기능이다.
ㅁ com.br.spring.ioc.java에 SpringMain 클래스 생성
package com.br.spring.ioc.java;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;
public class SpringMain {
public static void main ( String [] args ) {
AbstractApplicationContext ctx = new AnnotationConfigApplicationContext ( SpringBeanConfig . class );
Music m1 = ctx . getBean ( "music1" , Music . class );
Singer s1 = ctx . getBean ( "singer1" , Singer . class );
System . out . println (m1);
System . out . println (s1);
System . out . println ( "------------------------------------------------" );
Music m2 = ctx . getBean ( "music2" , Music . class );
Singer s2 = ctx . getBean ( "singer2" , Singer . class );
System . out . println (m2);
System . out . println (s2);
}
}
빌드 완료 전 기준으로, 클래스패스의 루트 디렉토리 는 다음과 같습니다:
Maven 프로젝트 : src/main/resources