최범균님의 "스프링5 프로그래밍 입문" 책을 정리하기 위해 작성된 내용입니다.
우분투로 실습을 진행하므로 터미널에
sudo apt install maven
이라고 치면 간단하게 maven 을 설치할 수 있다. 그리고 버전을 확인해보면
이런 화면을 확인할 수 있다.
스프링 5 프로그래밍 입문에서 제공하는 예제 코드를 github에서 clone 해온다.(책에서는 직접 작성하는 걸 전제로 설명하는 듯하다. 일단 잘 돌아가는지를 확인하기 위해 원래 있는 코드를 써보자)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>sp5</groupId>
<artifactId>sp5-chap02</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>utf-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
메이븐 프로젝트의 핵심은 pom.xml 파일로 루트 폴더에 위치하는 이 xml 파일은 메이븐 프로젝트에 대한 설정정보를 관리하는 파일로 프로젝트에서 필요로 하는 의존 모듈이나 플러그인에 대한 설정을 담고 있다.
여기에서 주요한 코드들을 설명하자면
위에서 pom 파일에 dependency 와 plugin 두가지 정보를 설정했다. Dependency 나 plugin 안에 모두 artifactId를 넣어준 모습을 확인할 수 있다.
메이븐은 모듈을 아티팩트라는 단위로 관리하여 위의 예로 보면 dependency는 spring-context 라는 식별자를 가진 5.0.2 RELEASE 버전의 아티팩트에 의존(모듈)을 추가한 것이다. 각 아티팩트의 완전한 이름은 아티팩트 이름-버전.jar
으로 위 설정은 메이븐 프로젝트의 소스코드를 컴파일하고 실행할 때 사용할 class path에 spring-context-5.0.2.RELEASE.jar 파일을 추가한다는 것을 의미한다.
이렇게 pom.xml 파일에 의존 설정을 추가했지만 저 spring-context-5.0.2.RELEASE.jar 파일은 로컬 어디에도 추가해준 기억이 없다 (개인적인 생각으로는 JDK 안에 있을 가능성이 높을 것 같지만...) . 위의 의존 설정을 추가해 준 파일을 추가하려 할 때 메이븐은 알아서 가져와 준다.
메이븐은 코드를 컴파일하거나 실행할 때 dependency
로 설정한 artifact파일을 사용한다. artifact 파일은 다음 과정을 거쳐 구한다.
메이븐은 기본적으로 [home]/.m2/repository
폴더를 local repository로 사용한다.
처음 mvn compile
을 실행하면 spring-context-5.0.2.RELEASE.jar파일 외에 다양한 아티팩트 파일을 다운로드하는 것을 확인할 수 있다. 이는 컴파일을 수행하는데 필요한 메이븐 디펜던시 파일을 가져올 때 그 파일이 또 의존하는 다른 파일을 포함해서 가져오게 된다.
예를 들어 spring-context-5.0.2.RELEASE.jar 이 파일을 가져오기 전에 spring-context-5.0.2.RELEASE.pom 파일을 다운로드하여 또 이 파일만의 의존설정이 있기 나름이다. 그러면 이 파일에 의존하는 파일들을 또 다운로드 받거나 로컬에서 찾아서 추가해주는 것이다.
간단한 스프링 예제를 작성하도록 하자. 작성할 파일의 목록은 다음과 같다.
public class Greeter {
private String format;
public String greet(String guest) {
return String.format(format, guest);
}
public void setFormat(String format) {
this.format = format;
}
}
Greeter class의 greet() 메서드는 string.format을 이용해서 새로운 문자열을 생성하여 반환한다.
@Configuration
public class AppContext {
@Bean
public Greeter greeter() {
Greeter g = new Greeter();
g.setFormat("%s, 안녕하세요!");
return g;
}
}
@Configuration 어노테이션은 해당 클래스를 스프링 설정 클래스로 지정한다는 의미이고, greeter() 함수는 스프링에서 관리하는 객체를 생성하고 초기화하는 설정을 담고 있다.
여기서 스프링이 관리하는 객체, 스프링이 생성하는 객체를 Bean객체라고 부르는데 이 bean 객체에 대한 정보를 담고 이쓴 메서드가 greeter() 메서드이다. 이 메서드에는 위에 나와있듯이 @Bean 어노테이션이 붙어 있어 해당 메서드가 생성한 객체를 스프링이 관리하는 bean 객체로 등록하게 된다.
이제 남은건 스프링이 제공하는 클래스를 이용하여 AppContext 를 읽어와 사용하는 main 함수가 필요하다.
public class Main{
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx =
new AnnotationConfigApplicationContext(AppContext.class);
Greeter g = ctx.getBean("greeter", Greeter.class);
String msg = g.greet("스프링");
System.out.println(msg);
ctx.close();
}
}
AnnotationConfigApplicationContext 클래스는 자바 설정에서 정보를 읽어와 bean 객체를 생성하고 관리한다. 이렇게 객체를 생성할 때 앞서 작성한 AppContext 클래스를 생성자 parameter로 전달한다.
AnnotationConfigApplicationcontext는 AppContext 에 정의한 @Bean 설정 정보를 읽어와 Greeter객체를 생성하고 초기화한다.
getBean() 메서드는 AnnotationConfigApplicationContext가 자바 설정을 읽어와 생성한 bean 객체를 검색할 때 사용된다. 해당 메서드의 첫 인자는 메서드 이름인 bean 객체으 이름이며 두번째 인자는 검색할 bean 객체의 type이다.
앞서 AppContext에서 @Bean 메서드의 이름이 "greeter" 이고 생서한 객체의 반환 타입이 Greeter이므로 ctx.getBean("greeter", Greeter.class);
이런식으로 메서드를 호출할 수 있다. 그럼 반환값으로 gretter() 메서드가 생성한 Gretter 객체를 받을 수 있다.
무슨 오류인지 잘 모르겠는데 자세히 살펴보면
Unable to make protected final java.lang.Class java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain) throws java.lang.ClassFormatError accessible: module java.base does not "opens java.lang" to unnamed module
이런식의 오류가 발생한다.
그래서 한나절 정도를 소모한뒤 살펴본 결과 JDK 버전을 eclipse에서 따로 내가 받은 JDK 8 버전으로 설정해줘야 된다는 사실을 배웠다.
아래의 링크를 보니 버전 문제인 것을 확인할 수 있다.
https://exerror.com/accessible-module-java-base-does-not-opens-java-io-to-unnamed-module/
사실 eclipse에서 기본적으로 1.8 버전으로 설정되어있는 것 같았는데, 내가 이전에 설치해둔 JDK 8 버전으로 프로젝트 설정을 변경하였더니 해결이 되었다.
프로젝트를 오른쪽 클릭해서 속성(Properties) 로 이동한다.
이 창에서 오른쪽에 Java Build Path
탭으로 간 다음 왼쪽에 Add Library...
를 눌러준다.
JRE System Library 를 눌러준 다음
Alternate JRE
를 체크한 다음 installed JREs...
를 누른다
이 창이 보이면 Add..
를 눌러 JRE 를 새로 등록하도록 하자.
Standard VM
하고 다음
Directory...
를 눌러 내가 설치해둔 JRE 폴더를 찾아 들어가준다.
참고로 Window 운영체제에 경로를 기본설정 그대로 JDK를 받았다면 C 드라이브 - ProgramFiles - Java 폴더 안에 있을 것이다.
설치한 자바 버전파일을 추가하면 그림과 같이 추가된 모습을 확인할 수 있다.
추가해준 jre를 체크하고 Apply and Close
-> Finish
를 해준다.
다 적용시키고 나와서 다시 run 을 하면 잘 되는 걸 확인할 수 있다.
앞서 간단한 스프링 프로그램을 작성하고 실행해봤다. 위에서 핵심은 AnnotationConfigApplicationContext 클래스이다.
Spring의 핵심은 객체를 생성하고 초기화하는 것이다. 이와 관련된 기능은 ApplicationContext라는 인터페이스에 정의되어 있어 AnnotationConfigApplicationContext 클래스는 이 인터페이스를 알맞게 구현한 클래스 중 하나로 자바 클래스에서 정보를 읽어와 객체 생성과 초기화를 수행한다.
AnnotationConfigApplicationContext 클래스의 계층도 일부를 표시한 그림이다. 참고로 실선으로 나타낸 관계가 부모-자식 상속관계이고 점선으로 나타낸 관계가 인터페이스-구현 클래스 관계이다.
보면 가장 상위에 BeanFactory 인터페이스가 위치하고 위에서 세번째에 ApplicationContext 인터페이스가 있다.
BeanFactory 인터페이스는 객체 생성과 검색에 대한 기능을 정의한다. 예를 들어 생성된 객체를 검색하는데 필요한 getBean() 메서드가 BeanFactory에 정의되어 있다. 객체를 검색하는것 이외에 싱글톤/프로토타입 bean인지 확인하는 기능도 제공한다.
ApplicationContext 인터페이스는 메세지, 프로필/환경변수 등을 처리할 수 있는 기능을 추가로 정의한다.
앞서 사용한 AnnotationConfigApplicationContext 클래스를 비롯해 계층도의 가장 하단에 위치한 3개의 클래스는 BeanFactory와 ApplicastionContext에 정의된 기능의 구현을 제공한다.
앞서 Bean 객체는 AnnotationConfigApplicationContext 객체가 생성하고 관리한다고 했었다. 이 클래스에 해당하는 객체는 해당 프로젝트 내에 단 한개만 존재하여 스프링이 관리하게 된다.
아래의 코드를 살펴보자
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx =
new AnnotationConfigApplicationContext(AppContext.class);
Greeter g1 = ctx.getBean("greeter", Greeter.class);
Greeter g2 = ctx.getBean("greeter", Greeter.class);
System.out.println("(g1 == g2) = " + (g1 == g2));
ctx.close();
}
}
getBean()
메서드를 통해 bean 객체 를 두번 불러온다. 그리고 불러온 bean 객체가 같은지 확인은 ==
연산자로도 가능하므로 해당 변수들이 가르키는 객체가 같은지 확인해보는 코드이다.
실행시켜 보자
(g1==g2)의 결과가 true라는 것은 g1과 g2가 같은 객체라는 것을 의미한다. 그래서
Greeter g1 = ctx.getBean("greeter", Greeter.class);
Greeter g2 = ctx.getBean("greeter", Greeter.class);
이 두 메서드 호출은 같은 객체를 리턴한다는 의미이다.
별도 설정을 하지 않을 경우 스프링은 한 개의 bean 객체만을 생성하며 이 때 bean 객체는 싱글톤 범위를 갖는다고 표현할 수 있다. 싱글톤은 단일 객체를 의미하는 단어로 스프링은 기본적으로 한 개의 @Bean 어노테이션에 대해 한 개의 bean 객체를 생성한다.