
빈(Bean)으로 등록할 클래스를 찾는 과정이다. 스프링의 컴포넌트 스캔을 사용하면 자동으로 클래스를 탐색하고 빈으로 등록한다.
@SpringBootApplication //@ComponentScan
@Bean 을 사용하는 예시
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration // 이 클래스가 스프링 설정 클래스임을 나타
public class MyConfiguration {
@Bean // 해당 메서드가 빈을 생성하는 메서드임을 나타냄, 빈이 여러 개라면 @Bean 메서드도 여러 개 정의해주어야 한다.
public MyBean myBean() {
return new MyBean();
}
}
XML을 사용하는 예시
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="myBean" class="com.example.MyBean"/>
</beans>
bean> 은 스프링 빈을 등록하는데 사용, id 속성은 빈의 고유 식별자이며, class 속성은 해당 빈을 생성하는 클래스의 경로를 나타낸다. 빈이 여러 개라면 도 여러 개 명시해주어야 한다.
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(basePackages = "com.example")
public class MyConfiguration {
}
@ComponentScan은 지정된 패키지 (com.example) 및 하위 패키지에서 @Component, @Service 등의 애너테이션이 지정된 클래스를 탐색하여 자동으로 스프링 빈으로 등록 한다.
컴포넌트 스캔의 대상이 되며 자동으로 빈으로 등록되는 어노테이션으로 클래스의 용도에 따라 달라진다. @Service, @Repository, @Controller, @Configuration은 내부적으로 @Component를 사용하고 있다.
import org.springframework.stereotype.Service;
@Service
public class UserService {
public void addUser() {
}
}
import org.springframework.stereotype.Repository;
@Repository
public class UserRepository {
public void saveUser() {
}
}
import org.springframework.stereotype.Controller;
import com.example.demo.service.UserService;
@Controller
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
public void addUser() {
userService.addUser();
}
}
옵션을 사용하여 스캔 대상을 제어할 수 있다.
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(basePackages = "com.example")
public class MyConfiguration {
}
/*
includeFilters와 excludeFilters 옵션을 사용
@MyComponent 애너테이션이 붙은 클래스를 스캔 대상으로 추가하고,
“Test”로 끝나는 클래스를 스캔 대상에서 제외
*/
@Configuration
@ComponentScan(
basePackages = "com.example",
includeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = MyComponent.class),
excludeFilters = @ComponentScan.Filter(type = FilterType.REGEX, pattern = ".*Test"))
public class Config {
}
동일한 클래스가 includeFilters와 excludeFilters에 모두 해당하는 경우 excludeFilters가 우선 순위를 가잔다. excludeFilters에 해당하는 클래스들은 최종적으로 스캔 대상에서 제외된다.
기본적으로 해당 클래스의 이름을 사용하지만, 명시적으로 빈의 이름을 지정할 수도 있다.
@SpringBootApplication 어노테이션이 붙은 클래스의 패키지와 하위 패키지를 컴포넌트 스캔 대상으로 지정한다. 해당 어노테이션은 메인 클래스에서 사용한다.
@SpringBootApplication 어노테이션은 내부적으로 @ComponentScan을 포함하고 있지만, 필요에 따라 스캔 대상을 추가하거나 변경해야 할 때에는 @ComponentScan 어노테이션을 직접 사용하여 스캔 대상을 지정할 수 있다.
// MyApplication 클래스의 패키지와 그 하위 패키지를 컴포넌트 스캔 대상으로 지정
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
의존 관계(Dependency)란 내가 변하면 상대방에게도 영향을 끼치는 관계를 의미한다.
// Car 클래스와 Engine 클래스가 의존 관계
public class Car {
private Engine engine;
public Car() {
this.engine = new Engine();
}
}
의존 객체를 직접 생성하는 것이 아니라 외부에서 해당 의존 객체를 주입받는 것
// 의존 객체를 직접 생성하는 방법
public class Car {
private Engine engine;
public Car() {
// 의존 객체를 직접 생성
this.engine = new Engine();
}
}
// 의존 객체를 주입받는 방법
public class Car {
private Engine engine;
// 생성자를 통해 의존 객체를 주입받음
public Car(Engine engine) {
this.engine = engine;
}
}
생서자를 통해 의존 객체를 주입받는 방법은 어떤 엔진을 사용할지 결정하는 권한을 외부에 위임하는 것과 같다. 스프링에는 빈(Bean)이라는 객체들이 있고 빈도 서로가 서로를 의존하고 있다. 빈의 의존 관계를 스프링에서 관리하는데, 이 때 의존 관계 주입(Dependency Injection) 방식을 사용하고 있으며, 스프링과 빈의 의존 관계가 이상하다면 오류를 발생시키기도 한다.
일반적으로 불변 보장, 테스트 용이성, 순환 참조 방지를 위해 생성자 주입 방식을 권장한다.
public class MyClass {
private MyDependency myDependency;
// @Autowired가 없는 생성자
public MyClass(String message) {
System.out.println("문자열을 받는 생성자 호출: " + message);
}
// @Autowired가 있는 생성자
@Autowired
public MyClass(MyDependency myDependency) {
this.myDependency = myDependency;
System.out.println("의존 객체를 받는 생성자 호출");
}
}
package com.estsoft.springprojecttest.controller;
import com.estsoft.springprojecttest.interf.InterDependencyService;
import com.estsoft.springprojecttest.service.HelloService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
// @Autowired
private HelloService helloService; // Dependency Injection (DI)
private InterDependencyService interDependencyService;
public HelloController(HelloService helloService, InterDependencyService interDependencyService) { // 생성자 주입 Autowired 지우고 작성, 위 코드랑 의도하는 바는 같음
this.helloService = helloService;
this.interDependencyService = interDependencyService;
}
@GetMapping("/hello") // 특정 패턴이 왔을 때 처리할 수 있도록
// http://localhost:8080/hello?param=jo
public String hello(@RequestParam(value = "param", defaultValue = "Spring") String param){
// 객체 직접 생성, 호출
// HelloService helloService = new HelloService();
// return helloService.printHello(param);
interDependencyService.printMethod();
// Spring 에게 제어권 맡기기(DI 사용해서)
return helloService.printHello(param);
}
}
public class MyClass {
@Autowired
private MyDependency myDependency;
}
public class MyClass {
private MyDependency myDependency;
@Autowired
public void setMyDependency(MyDependency myDependency) {
this.myDependency = myDependency;
}
}
불변 보장
public class MyClass {
// final 키워드 사용
private final MyDependency myDependency;
public MyClass(MyDependency myDependency) {
this.myDependency = myDependency;
}
}
테스트 용이성
순환 참조 방지
The dependencies of some of the beans in the application context form a cycle 와 같은 에러를 발생시킨다.스프링 부트 애플리케이션을 구성하기 위한 어노테이션
@SpringBootApplication
public class SpringBootDeveloperApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootDeveloperApplication.class, args);
}
}
자바의 main() 메서드와 같은 역할을 한다. 이 어노테이션을 추가하면 스프링 부트 사용에 필요한 기본 설정을 해준다. SpringApplication.run() 메서드는 애플리케이션을 실행하는데, 이 메서드의 첫 번째 인수는 스프링 부트 애플리케이션의 메인 클래스로 사용할 클래스, 두 번째 인수는 커맨드 라인의 인수들을 전달한다.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration // 스프링 부트 관련 설정
@ComponentScan(...)
@EnableAutoConfiguration // 자동으로 등록된 빈을 읽고 등록
public @interface SpringBootApplication {
...
}
사용자가 등록한 빈을 읽고 등록하는 어노테이션으로 @Component라는 애노테이션을 가진 클래스들을 찾아 빈으로 등록해주는 역할을 하지만, 모든 빈에 @Component만 사용하는건 아니다. 해당 어노테이션을 감싸는 어노테이션이 있으며 실제 개발을 하면 용도에 따라 아래와 같은 어노테이션으 사용한다.
| 어노테이션명 | 설명 |
|---|---|
| @Configuration | 설정 파일 등록 |
| @Repository | ORM 매핑 |
| @Controller, @RestController | 라우터 |
| @Service | 비즈니스 로직 |
스프링 부트에서 자동 구성을 활성화하는 어노테이션으로 스프링 부트 서버가 실행될 때 스프링 부트의 메타 파일을 읽고 정의된 설정들을 자동으로 구성하는 역할을 한다. spring.factories 파일에 클래스들이 모두 @EnableAutoConfiguration을 사용할 때 자동 설정된다.
라우터 역할을 하는 어노테이션으로 라우터란 HTTP 요청과 메서드를 연결하는 장치를 말한다. 해당 어노테이션이 있어야 클라이언트의 요청에 맞는 메서드를 실행할 수 있다.
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@GetMapping("/hello") // GET /hello 요청이 들어오면 test() 메서드 실행
public String test() {
return "Nice to meet you";
}
}
HelloController를 라우터로 지정해 /hello 라는 HTTP GET요청이 왔을 때 test() 메소드를 실행하도록 구성한다.
@Controller 어노테이션에서 @Component 어노테이션을 가지고 있었기 때문에 @Controller 어노테이션이 @ComponentScan을 통해 빈으로 등록된다. @Configuration, @Repository, @Service 어노테이션 모두 @Component 어노테이션을 가지고 있다. → 빈이 무슨 역할을 하는지 명확하게 구분하기 위해 다른 이름으로 덮어두었다.
interface 타입으로 의존성 주입(DI)할 때 구현체 지정하는 방법
package com.estsoft.springprojecttest.interf;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
@Service
public class InterDependencyService {
private final Inter inter;
// interface 타입으로 의존성 주입(DI)할 때 구현체 지정하는 방법 2가지
// (단, interface의 구현체가 하나일 경우에는 구현체 지정하지 않아도 DI 가능)
// 1. @Qualifier ("빈 이름(구현체) 지정") ex.@Qualifier("interImplA")
// 2, @Primaty
public InterDependencyService(@Qualifier("interImplA") Inter inter) {
this.inter = inter;
}
public void printMethod() {
inter.method();
}
}
package com.estsoft.springprojecttest.interf;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;
@Primary
@Service
public class InterlmplA implements Inter{
@Override
public void method(){
System.out.println("Hi A");
}
}
package com.estsoft.springprojecttest.interf;
import org.springframework.stereotype.Service;
@Service
public class InterlmplB implements Inter{
@Override
public void method(){
System.out.println("Hi B");
}
}