메이븐 프로젝트 생성
메이븐
[Spring] Maven과 Gradle
빌드 관리 도구빌드소스 코드 파일을 컴파일에서 실행할 수 있는 가공물로 변환하는 과정 또는 결과물우리가 작성한 소스코드(java), 프로젝트에서 쓰인 각각의 파일 및 자원 등(.xml, jpa, jpg, proper
best11gh.tistory.com
- 사용하는 프레임워크에 관계없이 앱의 빌드 프로세스를 쉽게 관리하는 데 사용하는 도구
- 실제 시나리오에서 스프링 프로젝트에 가장 많이 사용되는 빌드 도구 중 하나이다.
- GroupId : 관련된 여러 프로젝트를 그룹화하는 데 사용
- ArtifactId : 현재 애플리케이션 이름
- Version : 현재 구현 상태의 식별자, 프로젝트를 설정할 때 버전 설정이 나오지 않는다면 생성 후 pox.xml 파일에서 설정할 수 있다.
메이븐 프로젝트 구성
- src 폴더: 소스 폴더라고도 하며, 앱에 속한 모든 것을 넣을 수 있다.
- main 폴더: 애플리케이션의 소스 코드 저장. 자바 코드와 구성 정보가 java 및 resources라는 두 하위 폴더에 개별적으로 포함된다.
- test 폴더: 단위 테스트의 소스 코드 저장
- pom.xml 파일: 새 종속성 추가처럼 메이븐 프로젝트 구성을 작성하는 파일
pom.xml 파일의 기본 내용
<?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>org.example</groupId>
<artifactId>gsws_ap_b_ex1</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>
기본 pom.xml 파일을 사용하면 프로젝트에서는 외부 의존성으로 JDK만 활용한다. 프로젝트의 외부 의존성 폴더(External Libraries)를 보면 JDK만 표시된다.
외부 의존성 추가
모든 의존성을 <dependencies>와 </dependencies> 태그 사이에 작성한다. 각 의존성은 의존성 속성(의존성의 그룹 ID, 아티팩트 이름, 버전)을 작성하는 <dependencies>와 </dependencies> 태그 집합으로 나타낸다.
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>6.1.6</version>
</dependency>
</dependencies>
메이븐은 이 세 가지 속성에 대해 제공한 값으로 의존성을 검색하고 리포지토리에서 의존성을 내려받는다. 기본적으로 'Maven central'이라는 리포지토리에 의존성(일반적으로 JAR 파일)을 내려받는 점만 알아 두면 된다.
스프링 컨텍스트에 새로운 빈 추가
스프링 컨텍스트 : 스프링이 관리하기 위한 인스턴스를 담을 바구니
스프링 컨텍스트에 빈(bean)을 추가하는 방법은 여러 가지가 있으며, 스프링이 빈을 관리하고 제공하는 기능을 앱에 플러그인할 수 있다. 작업에 따라 빈을 추가하는 특정 방법을 선택하게 된다.
컨텍스트에 빈 추가하는 방법
- @Bean 애너테이션 사용
- 스테레오타입(stereotype) 애너테이션 사용
- 스프링 컨테이너가 스프링 관리 컴포넌트로 식별하게 해 주는 단순한 표시
- @Component, @Controller, @Service, @Repository 등이 해당된다.
- 프로그래밍 방식
1. Parrot 타입의 객체와 스프링 컨텍스트 생성
Parrot 클래스
public class Parrot {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Parrot 클래스 인스턴스 생성
public class Main {
public static void main(String[] args) {
Parrot p = new Parrot();
}
}
스프링 컨텍스트 인스턴스 생성
public class Main {
public static void main(String[] args) {
var context = new AnnotationConfigApplicationContext();
Parrot p = new Parrot();
}
}
Parrot 인스턴스를 생성했지만 스프링 컨텍스트 외부에 있다.
스프링 컨텍스트에 Parrot 인스턴스를 추가하면 스프링이 해당 인스턴스를 볼 수 있다.
2-1. @Bean 애너테이션을 사용하여 스프링 컨텍스트에 빈 추가
프로젝트에 정의된 클래스의 인스턴스뿐만 아니라 직접 만들지는 않았지만 앱에서 사용하는 클래스도 추가할 수 있다. 스프링이 빈 일부인 객체만 관리할 수 있기 때문에 스프링에서 빈을 추가하는 방법을 배워야 한다.
1단계: 프로젝트에서 구성 클래스 정의하기
@Configuration
public class ProjectConfig {
}
- @Configuration 애너테이션을 사용하여 이 클래스를 스프링 구성 클래스로 정의한다.
- 구성 클래스가 할 수 있는 일 중 하나는 스프링 컨텍스트에 빈을 추가하는 것이다.
2단계: 빈을 반환하는 메서드를 생성하고 @Bean 애너테이션을 메서드에 추가하기
스프링 컨텍스트에 빈을 추가할려면 컨텍스트에 추가하려는 객체 인스턴스를 반환하는 메서드를 정의하고 그 메서드에 @Bean 애너테이션을 추가해야 한다.
→ 스프링에 스프링 컨텍스트를 초기화할 때 이 메서드를 호출해야 하고 반환된 값을 컨텍스트에 추가해야 한다고 지시하게 된다.
@Configuration
public class ProjectConfig {
@Bean
Parrot parrot() {
var p = new Parrot();
p.setName("Koko");
return p;
}
}
- @Bean 애너테이션을 추가하여 스프링에 컨텍스트가 초기화될 때 이 메서드를 호출하고 반환된 값을 컨텍스트에 추가하라고 지시한다.
- 메서드에 사용한 이름에는 동사가 포함되어 있지 않아야 한다.
3단계: 새로 생성된 구성 클래스로 스프링이 컨텍스트를 초기화하도록 만들기
스프링이 컨텍스트를 초기화할 때 구성 클래스를 사용하도록 해야 한다.
public class Main {
public static void main(String[] args) {
// 스프링 컨텍스트 인스턴스가 생성될 때 구성 클래스를 매개변수로 전송하여 스프링이 일르 사용하도록 지시
var context = new AnnotationConfigApplicationContext(ProjectConfig.class);
// Parrot 인스턴스가 실제로 컨텍스트에 포함되었는지 확인
Parrot p = context.getBean(Parrot.class); // 스프링 컨텍스트에서 Parrot 타입의 빈 참조를 가져온다.
System.out.println(p.getName()); // Koko 출력
}
}
여러 개의 빈 추가
다양한 타입의 빈 추가
@Configuration
public class ProjectConfig {
@Bean
Parrot parrot() {
var p = new Parrot();
p.setName("Koko");
return p;
}
// 스프링 컨텍스트에 "Hello" 문자열 추가
@Bean
String Hello(){
return "Hello";
}
// 스프링 컨텍스트에 정수 10 추가
@Bean
Integer ten(){
return 10;
}
}
public class Main {
public static void main(String[] args) {
var context = new AnnotationConfigApplicationContext(ProjectConfig.class);
Parrot p = context.getBean(Parrot.class);
System.out.println(p.getName());
String s = context.getBean(String.class);
System.out.println(s);
Integer n = context.getBean(Integer.class);
System.out.println(n);
}
}
- 명시적으로 형 변환은 할 필요가 없다. 스프링은 요청받은 빈 타입을 컨텍스트에서 찾는다. 그러한 빈이 없으면 스프링은 예외를 던진다.
- Koko Hello 10이 출력된다.
동일한 타입의 빈 추가
@Configuration
public class ProjectConfig {
@Bean
Parrot parrot1() {
var p = new Parrot();
p.setName("Koko");
return p;
}
@Bean
Parrot parrot2() {
var p = new Parrot();
p.setName("Miki");
return p;
}
@Bean
Parrot parrot3() {
var p = new Parrot();
p.setName("Riki");
return p;
}
}
타입만 지정해서는 더 이상 컨텍스트에서 빈을 가져올 수 없다. 스프링이 선언한 인스턴스 중 어떤 것을 참조할지 짐작할 수 없기 때문에 예외가 발생한다. 이런 모호성을 해결하려면 빈 이름을 사용해서 인스턴스 중 하나를 정확하게 참조해야 한다.
식별자로 빈 참조하기
public class Main {
public static void main(String[] args) {
var context = new AnnotationConfigApplicationContext(ProjectConfig.class);
Parrot p = context.getBean("parrot2" ,Parrot.class); // 첫 번째 매개변수가 참조할 인스턴스 이름이다.
System.out.println(p.getName());
}
}
빈에 다른 이름 지정
- @Bean(name="miki")
- @Bean(value="miki")
- @Bean("miki")
@Primary
@Bean
@Primary
Parrot parrot2() {
var p = new Parrot();
p.setName("Miki");
return p;
}
- @Primary를 통해 Parrot 타입의 빈이 여러 개 정의되어있을 때 parrot2 메소드에서 생성된 빈이 기본적으로 사용되도록 한다.
2-2. 스테레오타입 애너테이션으로 스프링 컨텍스트에 빈 추가
스테레오타입 애너테이션을 사용하려면 스프링 컨텍스트에 추가해야 할 인스턴스의 클래스 위에 애너태이션을 추가해야 한다. 앱이 스프링 컨텍스트를 생성하면 스프링은 컴포넌트로 표시된 클래스의 인스턴스를 생성하고 해당 인스턴스를 컨텍스트에 추가한다.
이 방식을 사용하면 스테레오타입 애너테이션으로 지정된 클래스를 찾을 위치를 스프링에 알려 주는 구성(configuration) class가 필요하다.
1. @Component 애너테이션으로 스프링이 해당 컨텍스트에 인스턴스를 추가할 클래스를 표시한다.
@Component // 스프링은 이 클래스의 인스턴스를 생성하고 스프링 컨텍스트에 추가한다
public class Parrot {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
기본적으로 스프링은 스테레오타입 애너테이션으로 지정된 클래스를 검색하지 않기 때문에 코드를 두면 스프링은 스프링 컨텍스트에 Parrot타입의 빈을 추가하지 않는다.
→ 구성 클래스에 @ComponentScan을 추가해야 한다.
2. 구성 클래스 위에 @ComponentScan 애너테이션으로 표시한 클래스를 어디에서 찾을 수 있는지 스프링에 지시한다.
@Configuration
@ComponentScan(basePackages = "model") // 스프링에 스테레오타입 애너테이션이 지정된 클래스를 찾을 위치를 알려 준다.
public class ProjectConfig {
}
public class Main {
public static void main(String[] args) {
var context = new AnnotationConfigApplicationContext(ProjectConfig.class);
Parrot p = context.getBean(Parrot.class);
System.out.println(p); // 스프링 컨텍스트에서 가져온 인스턴스를 기본 String 형식으로 출력 - model.Parrot@3a93b025
System.out.println(p.getName()); // 스프링이 컨텍스트에 추가한 Parrot 인스턴스에 아직 이름을 설정하지 않았기 때문에 null 출력
}
}
PostConstruct를 사용하여 인스턴스 생성 후 관리
@Bean을 사용하면 스프링 컨텍스트에 추가한 각 Parrot의 인스턴스의 이름을 정의할 수 있지만, @Component를 사용하면 스프링이 Parrot 클래스의 생성자를 호출한 후에는 어떤 것도 할 수 없다.
스프링이 빈을 생성한 직후 몇 가지 명령어를 실행하려면 자바 EE에서 유래한 PostConstruct 애너테이션을 사용할 수 있다.
컴포넌트 클래스에서 메서드를 정의하고 해당 메서드에 @PostConstruct 애너테이션을 추가하면 생성자가 실행을 완료한 후 스프링이 해당 메서드를 호출하도록 지시할 수 있다.
@Component
public class Parrot {
private String name;
@PostConstruct
public void init() {
this.name = "Kiki";
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
2-3. 프로그래밍 방식으로 스프링 컨텍스트에 빈 추가
컨텍스트 인스턴스의 메서드를 호출하여 컨텍스트에 새 인스턴스를 직접 추가할 수 있어 유연성이 매우 뛰어나다. @Bean 또는 스테레오타입 애너테이션 방식이 요구에 충족되지 않을 때는 이 방식을 사용한다.
애플리케이션의 특정 구성 정보에 따라 스프링 컨텍스트에 특정 빈을 등록해야 한다고 가정했을 때, @Bean 및 스테레오타입 애너테이션을 사용하면 시나리오 대부분을 구현할 수 있지만 다음 코드에서 제시된 작업은 수행할 수 없다.
if (condition) {
registerBean(b1); // 조건이 참이면 스프링 컨텍스트에 특정 빈을 추가한다.
} else {
registerBean(b2); // 참이 아니면 스프링 컨텍스트에 다른 빈을 추가한다.
}
프로그래밍 방식으로 스프링 컨택스트에 빈을 추가하려면 ApplicationContext 인스턴스의 registerBean() 메서드를 호출만 하면 된다.
<T> void registerBean(
String beanName,
Class<T> beanClass,
Supplier<T> supplier,
BeanDefinitionCustomizer... customizers);
- beanName: 스프링 컨텍스트에서 추가할 빈 이름 정의. 지정할 필요가 없다면 메서드를 호출할 때 null 값을 사용할 수 있다.
- beanClass: 컨텍스트에 추가할 빈을 정의하는 클래스. Parrot 클래스의 인스턴스를 추가하고 싶다면 이 매개변수에 지정할 값은 Parrot.class가 된다.
- supplier: Supplier의 인스턴스. 이 Supplier의 구현체는 컨텍스트에 추가할 인스턴스 값을 전달해야 한다. Supplier 구현체의 목적은 매개변수 없이 사용자가 정의한 값을 반환하는 것이다.
- BeanDefinitionCustomizer의 varargs: varargs 타입으로 정의되므로 이를 완전히 생략하거나 BeanDefinitionCustomizer 타입의 값을 더 지정할 수 있다.
public class Main {
public static void main(String[] args) {
var context = new AnnotationConfigApplicationContext(ProjectConfig.class);
Parrot x = new Parrot(); // 스프링 컨텍스트에 추가하고 싶은 인스턴스 생성
x.setName("Kiki");
Supplier<Parrot> parrotSupplier = () -> x; // 이 인스턴스를 반환할 Supplier 정의
// registerBean() 메서드를 호출하여 이 인스턴스를 스프링 컨텍스트에 추가한다.
//context.registerBean("parrot1", Parrot.class, parrotSupplier);
// 기본 빈 정의(컨텍스트에 동일한 타입의 빈이 여러 개 있을 때 기본적으로 선택할 인스턴스 정의)
context.registerBean("parrot1",
Parrot.class,
parrotSupplier,
bc -> bc.setPrimary(true));
Parrot p = context.getBean(Parrot.class);
System.out.println(p.getName());
}
}
@Bean 애너테이션 vs 스테레오타입 애너테이션
실제 시나리오에서는 가능한 한 스테레오타입 애너테이션을 사용하고(이 방식이 코드 작성량이 적기 때문), 다른 방법으로 빈을 추가할 수 없을 때만 @Bean을 사용한다(예를 들어 라이브러리 일부로 포함된 클래스에 대한 빈을 생성하므로, 해당 클래스에 스테레오타입 애너테이션 추가할 수 없는 경우).
@Bean 애너테이션 사용
- 스프링 컨텍스트에 추가할 인스턴스의 생성을 완전히 제어할 수 있다. @Bean이 달린 메서드 안에서 인스턴스를 생성하고 구성하는 것은 사용자 책임이다. 스프링은 해당 인스턴스만 받아 컨텍스트에 그대로 추가한다.
- 이 메서드를 사용하면 동일한 타입의 인스턴스를 스프링 컨텍스트에 더 추가할 수 있다.
- @Bean 애너테이션을 사용하여 스프링 컨텍스트에 모든 객체 인스턴스를 추가할 수 있다. 즉 인스턴스를 정의한 클래스가 앱 내에서 정의되지 않아도 추가할 수 있다. (e.g String과 Integer 타입)
- 생성하는 각 빈에 대해 별도의 메서드를 작성해야 하므로 앱에 상용구 코드가 추가된다. 이러한 이유로 프로젝트에서는 @Bean을 사용하기보다는 스테레오타입 애너테이션을 선호한다.
스테레오타입 애너테이션 사용
- 프레임워크가 인스턴스를 생성한 후에만 인스턴스를 제어할 수 있다.
- 이렇게 하면 컨텍스트에 클래스의 인스턴스를 하나만 추가할 수 있다.
- 스테레오타입 애너테이션은 애플리케이션이 소유한 클래스의 빈을 생성하는 데만 사용할 수 있다. (변경할 클래스를 소유하고 있지 않기 때문에 String이나 Integer 같은 타입의 빈을 추가할 수 없다.)
- 스테레오타입 애너테이션을 사용하면 스프링 컨텍스트에 빈을 추가해도 앱에 상용구 코드가 추가되지 않는다. 일반적으로 앱에 속한 클래스에서는 이 방식을 선호할 것 이다.
잡담
스프링 컨텍스트랑 빈에 대해서 알아보기
supplier
'Spring > 스프링 교과서' 카테고리의 다른 글
[스프링 교과서] 4장 스프링 컨텍스트: 추상화 (0) | 2024.08.08 |
---|---|
[스프링 교과서] 3장 스프링 컨텍스트: 빈 작성 (0) | 2024.07.15 |
[스프링 교과서] 1장 현실 세계의 스프링 (0) | 2024.07.08 |