728x90
반응형

1. Proxy

Java에서 프록시(Proxy)는 대리자를 의미한다. 프록시는 기존의 객체를 감싸서 그 객체의 기능을 확장하거나 변경할 수 있게 해준다. 예를 들어, 프록시 객체를 사용하면 객체에 대한 접근을 제어하거나, 객체의 메소드 호출 전후에 로깅 작업 등을 수행할 수 있다. 또한, 프록시 객체를 사용하여 원격으로 실행되는 객체를 호출할 수도 있다. 프록시는 주로 AOP(Aspect Oriented Programming)에서 사용된다.
  • 프록시 생성은 크게 두 가지 방식이 제공된다.
  1. 1. JDK Dynamic Proxy 방식
    • 리플렉션을 이용해서 proxy 클래스를 동적으로 생성해주는 방식으로, 타겟의 인터페이스를 기준으로 proxy를 생성해준다. 사용자의 요청이 타겟을 바라보고 실행될 수 있도록 타겟 자체에 대한 코드 수정이 아닌 리플렉션을 이용한 방식으로, 타겟의 위임 코드를InvocationHandler를 이용하여 작성하게 된다. 하지만 사용자가 타겟에 대한 정보를 잘못 주입하는 경우가 발생할 수 있기 때문에 내부적으로 주입된 타겟에 대한 검증 코드를 거친 후 invoke가 동작하게 된다.
  2. CGLib 방식
    • 동적으로 Proxy를 생성하지만 바이트코드를 조작하여 프록시를 생성해주는 방식이다. 인터페이스 뿐 아니라 타겟의 클래스가 인터페이스를 구현하지 않아도 프록시를 생성해준다. CGLib(Code Generator Library)의 경우에는 처음 메소드가 호출된 당시 동적으로 타켓 클래스의 바이트 코드를 조작하게 되고, 그 이후 호출 시부터는 변경된 코드를 재사용한다. 따라서 매번 검증 코드를 거치는 1번 방식보다는 invoke시 더 빠르게 된다. 또한 리플렉션에 의한 것이 아닌 바이트코드를 조작하는 방식이기 때문에 성능면에서는 더 우수하다.

하지만 CGLib 방식은 스프링에서 기본적으로 제공되는 방식은 아니었기에 별도로 의존성을 추가하여 개발해야 했고, 파라미터가 없는 default 생성자가 반드시 필요했으며, 생성된 프록시의 메소드를 호출하면 타겟의 생성자가 2번 호출되는 등의 문제점들이 있었다.

스프링 4.3, 스프링부트 1.3 이후부터 CGLib의 문제가 된 부분이 개선되어 기본 core 패키지에 포함되게 되었고, 스프링에서 기본적으로 사용하는 프록시 방식이 CGLib 방식이 되었다.

 

1. 로직을 포함하는 코드 작성

  • Student 인터페이스 작성
public interface Student {
	
	void study(int hours);
}

 

  • Student 클래스 작성 (Student인터페이스 구현)
public class Student implements Student {
	
	 @Override
     public void study(int hours) {
        System.out.println(hours + "시간 동안 열심히 공부합니다.");
     }
}

 

2. dynamic

1. Handler 클래스 작성

java.lang.reflect.InvocationHandler를 구현한 클래스를 작성한다.

Student 클래스를 타겟 인스턴스로 설정하고 invoke 메소드를 정의한다.

public class Handler implements InvocationHandler {
	
	/* 메소드 호출을 위해 타겟 인스턴스가 필요하다 */
	private final Student student;
     
  public Handler(Student student) {
    this.student = student;
  }
    
  /* 생성된 proxy 인스턴스와 타겟 메소드, 전달받은 인자를 매개변수로 한다. */
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) 
		throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
    	
    System.out.println("============ 공부가 너무 하고 싶습니다. ==============");
    System.out.println("호출 대상 메소드 : " + method);
    for(Object arg : args) {
    	System.out.println("전달된 인자 : " + arg);
    }
    	
   /* 타켓 메소드를 호출한다. 타겟 Object와 인자를 매개변수로 전달한다. 
    * 여기서 프록시를 전달하면 다시 타겟을 호출할 때 다시 프록시를 생성하고 다시 또 전달하는 무한 루프에 빠지게 된다.
    * */
   method.invoke(student, args);
    	 
   System.out.println("============ 공부를 마치고 수면 학습을 시작합니다. ============");
    	 
   return proxy;
    	 
  }
}

 

2. Application 실행 클래스 작성

Student student = new Student();
Handler handler = new Handler(student);
		
/* 클래스로더, 프록시를 만들 클래스 메타 정보(인터페이스만 가능), 프록시 동작할 때 적용될 핸들러 */
Student proxy 
= (Student) Proxy.newProxyInstance(Student.class.getClassLoader(), new Class[] {Student.class}, handler);
	    
/* 프록시로 감싸진 인스턴스의 메소드를 호출하게 되면 핸들러에 정의한 메소드가 호출된다. */
proxy.study(16);

/*
============ 공부가 너무 하고 싶습니다. ==============
호출 대상 메소드 : public abstract void Student.study(int)
전달된 인자 : 16
16시간 동안 열심히 공부합니다.
============ 공부를 마치고 수면 학습을 시작합니다. ============
*/

 

  • study 메소드 호출 시 proxy 객체의 동작을 확인할 수 있다.

 

3. cglib

1. Handler 클래스 작성

org.springframework.cglib.proxy.InvocationHandler를 구현한 클래스를 작성한다.

Student 클래스를 타겟 인스턴스로 설정하고 invoke 메소드를 정의한다.

public class Handler implements InvocationHandler {
	
	private final Student student;
     
  public Handler(Student student) {
   this.student = student;
  }
    
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) 
		throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
    	
  	System.out.println("============ 공부가 너무 하고 싶습니다. ==============");
   	System.out.println("호출 대상 메소드 : " + method);
   	for(Object arg : args) {
   		System.out.println("전달된 인자 : " + arg);
   	}
    	
   	method.invoke(student, args);
    	 
   	System.out.println("============ 공부를 마치고 수면 학습을 시작합니다. ============");
    	 
   	return proxy;
   	 
  }
}

 

2. Application 실행 클래스 작성

Student student = new Student();
Handler handler = new Handler(student);
		
/* Enhancer 클래스의 create static 메소드는 타겟 클래스의 메타정보와 핸들러를 전달하면 proxy를 생성해서 반환해준다. */
Student proxy 
= (Student) Enhancer.create(Student.class, new Handler(new Student()));

proxy.study(20);

/*
============ 공부가 너무 하고 싶습니다. ==============
호출 대상 메소드 : public void Student.study(int)
전달된 인자 : 20
20시간 동안 열심히 공부합니다.
============ 공부를 마치고 수면 학습을 시작합니다. ============
*/
  • study 메소드 호출 시 proxy 객체의 동작을 확인할 수 있다.
728x90
반응형
728x90
반응형

[Spring Framework] Spring Framework에서 @Inject 애너테이션 완벽 가이드

 

[Spring Framework] Spring Framework에서 @Inject 애너테이션 완벽 가이드

[Spring Framework] Spring Boot DI 완벽 정리 | @Resource 활용법 [Spring Framework] Spring Boot DI 완벽 정리 | @Resource 활용법[Spring Framework] Spring Boot DI 핵심 가이드 | @Primary와 @Qualifier 제대로 이해하기 [Spring Framework]

crushed-taro.tistory.com

1. Bean

아래 코드는 이번 차시의 테스트에 공통적으로 사용할 Product , Beverage , Bread , ShoppingCart 클래스이다.

 

  • Product(abstract class)
public abstract class Product {
	
	private String name;	//상품명
	private int price;		//상품가격
	
	public Product() {}

	public Product(String name, int price) {
		super();
		this.name = name;
		this.price = price;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getPrice() {
		return price;
	}

	public void setPrice(int price) {
		this.price = price;
	}

 

  • Beverage
public class Beverage extends Product {
	
	private int capacity;	//용량
	
	public Beverage() {
		super();
	}
	
	public Beverage(String name, int price, int capacity) {
		super(name, price);
		this.capacity = capacity;
	}
	
	public int getCapacity() {
		return this.capacity;
	}
	
	public void setCapacity(int capacity) {
		this.capacity = capacity;
	}
	
	@Override
	public String toString() {
		return super.toString() + " " + this.capacity;
	}
}

 

  • Bread
public class Bread extends Product {
	
	private java.util.Date bakedDate;	//생산시간
	
	public Bread() {
		super();
	}
	
	public Bread(String name, int price, java.util.Date bakedDate) {
		super(name, price);
		this.bakedDate = bakedDate;
	}
	
	public java.util.Date getBakedDate() {
		return this.bakedDate;
	}
	
	public void setBakedDate(java.util.Date bakedDate) {
		this.bakedDate = bakedDate;
	}
	
	@Override
	public String toString() {
		return super.toString() + " " + this.bakedDate;
	}
}

 

  • ShoppingCart
public class ShoppingCart {
	
	private final List<Product> items;	//쇼핑카트에 담긴 상품들
	
	public ShoppingCart() {
		items = new ArrayList<>();
	}
	
	public void addItem(Product item) {
		items.add(item);
	}
	
	public List<Product> getItem() {
		return items;
	}
}

 

1. Bean Scope

bean scope란, 스프링 빈이 생성될 때 생성되는 인스턴스의 범위를 의미한다. 스프링에서는 다양한 bean scope를 제공한다.

Bean Scope Description
Singleton 하나의 인스턴스만을 생성하고, 모든 빈이 해당 인스턴스를 공유하여 사용한다.
Prototype 매번 새로운 인스턴스를 생성한다.
Request HTTP 요청을 처리할 때마다 새로운 인스턴스를 생성하고, 요청 처리가 끝나면 인스턴스를 폐기한다. 웹 애플리케이션 컨텍스트에만 해당된다.
Session HTTP 세션 당 하나의 인스턴스를 생성하고, 세션이 종료되면 인스턴스를 폐기한다. 웹 애플리케이션 컨텍스트에만 해당된다.

 

1. singleton

Spring Framework에서 Bean의 기본 스코프는 singleton이다. singleton은 애플리케이션 내에서 하나의 인스턴스만을 생성하고, 모든 빈이 해당 인스턴스를 공유하여 사용한다. 이를 통해 메모리 사용량을 줄일 수 있으며, 성능 향상을 기대할 수 있다.

 

@Configuration
public class ContextConfiguration {
	
	@Bean
	public Product carpBread() {
		
		return new Bread("붕어빵", 1000, new java.util.Date());
	}
	
	@Bean
	public Product milk() {
		
		return new Beverage("딸기우유", 1500, 500);
	}
	
	@Bean
	public Product water() {
		
		return new Beverage("지리산암반수", 3000, 500);
	}
	
	@Bean
	@Scope("singleton")		//기본값
	public ShoppingCart cart() {
		
		return new ShoppingCart();
	}
}

ContextConfiguration 설정 파일에Bread 타입의 붕어빵과 Beaverage 타입의 딸기우유와 지리산암반수, ShoppingCart 타입의 쇼핑카트를 bean으로 등록 한다. @Scope 어노테이션에 기본 값에 해당하는 singleton설정도 기재하였다.

 

/* 빈 설정 파일을 기반으로 IoC 컨테이너 생성 */
ApplicationContext context = new AnnotationConfigApplicationContext(ContextConfiguration.class);

/* 붕어빵, 딸기우유, 지리산 암반수 등의 빈 객체를 반환 받는다. */
Product carpBread = context.getBean("carpBread", Bread.class);
Product milk = context.getBean("milk", Beverage.class);
Product water = context.getBean("water", Beverage.class);

/* 첫 번째 손님이 쇼핑 카트를 꺼낸다. */
ShoppingCart cart1 = context.getBean("cart", ShoppingCart.class);
cart1.addItem(carpBread);
cart1.addItem(milk);

/* 붕어빵과 딸기우유가 담겨있다. */
System.out.println("cart1에 담긴 내용 : " + cart1.getItem());

/* 두 번째 손님이 쇼핑 카트를 꺼낸다. */		
ShoppingCart cart2 = context.getBean("cart", ShoppingCart.class);
cart2.addItem(water);

/* 붕어빵과 딸기우유와 지리산암반수가 담겨있다. */
System.out.println("cart2에 담긴 내용 : " + cart2.getItem());
		
/* 두 카드의 hashcode를 출력해보면 동일한 것을 볼 수 있다. */
System.out.println("cart1의 hashcode : " + cart1.hashCode());
System.out.println("cart2의 hashcode : " + cart2.hashCode());

/*
cart1에 담긴 내용 : [붕어빵 1000 Thu Jun 08 21:58:27 KST 2023, 딸기우유 1500 500]
cart2에 담긴 내용 : [붕어빵 1000 Thu Jun 08 21:58:27 KST 2023, 딸기우유 1500 500, 지리산암반수 3000 500]
cart1의 hashcode : 696933920
cart2의 hashcode : 696933920
*/

이 예제에서 손님 두 명이 각각 쇼핑 카트를 이용해 상품을 담는다고 가정했지만singleton으로 관리되는 cart는 사실 하나의 객체이므로 두 손님이 동일한 카트에 물건을 담는 상황이 발생한다. 상황에 따라서 기본 값인 singleton 스코프가 아닌 prototype 스코프가 필요할 수 있다. 

 

2. prototype

prototype 스코프를 갖는 Bean은 매번 새로운 인스턴스를 생성한다. 이를 통해 의존성 주입 등의 작업에서 객체 생성에 대한 부담을 줄일 수 있다.
@Configuration
public class ContextConfiguration {
	
	@Bean
	public Product carpBread() {
		
		return new Bread("붕어빵", 1000, new java.util.Date());
	}
	
	@Bean
	public Product milk() {
		
		return new Beverage("딸기우유", 1500, 500);
	}
	
	@Bean
	public Product water() {
		
		return new Beverage("지리산암반수", 3000, 500);
	}
	
	@Bean
	@Scope("prototype")		//기본 값에서 변경
	public ShoppingCart cart() {
		
		return new ShoppingCart();
	}

}

이전 예제와 동일하게 ContextConfiguration 설정 파일에 Bread 타입의 붕어빵과 Beaverage 타입의 딸기우유와 지리산암반수, ShoppingCart 타입의 쇼핑카트를 bean으로 등록 한다. 단, ShoppingCart의 경우 @Scope 어노테이션에 기본 값이 아닌 해당하는 prototype설정도 기재하였다.

 

/* 빈 설정 파일을 기반으로 IoC 컨테이너 생성 */
ApplicationContext context = new AnnotationConfigApplicationContext(ContextConfiguration.class);

/* 붕어빵, 딸기우유, 지리산 암반수 등의 빈 객체를 반환 받는다. */
Product carpBread = context.getBean("carpBread", Bread.class);
Product milk = context.getBean("milk", Beverage.class);
Product water = context.getBean("water", Beverage.class);

/* 첫 번째 손님이 쇼핑 카트를 꺼낸다. */
ShoppingCart cart1 = context.getBean("cart", ShoppingCart.class);
cart1.addItem(carpBread);
cart1.addItem(milk);

/* 붕어빵과 딸기우유가 담겨있다. */
System.out.println("cart1에 담긴 내용 : " + cart1.getItem());

/* 두 번째 손님이 쇼핑 카트를 꺼낸다. */		
ShoppingCart cart2 = context.getBean("cart", ShoppingCart.class);
cart2.addItem(water);

/* 지리산암반수가 담겨있다. */
System.out.println("cart2에 담긴 내용 : " + cart2.getItem());
		
/* 두 카드의 hashcode를 출력해보면 다른 것을 볼 수 있다. */
System.out.println("cart1의 hashcode : " + cart1.hashCode());
System.out.println("cart2의 hashcode : " + cart2.hashCode());

/*
cart1에 담긴 내용 : [붕어빵 1000 Thu Jun 08 21:58:40 KST 2023, 딸기우유 1500 500]
cart2에 담긴 내용 : [지리산암반수 3000 500]
cart1의 hashcode : 1946988038
cart2의 hashcode : 1990519794
*/

ShoppingCart의 bean scope를prototype으로 설정하자 getBean으로 인스턴스를 꺼내올 때 마다 새로운 인스턴스를 생성하게 된다. 따라서 이번 예제에서는 손님 두 명이 각각 쇼핑 카트를 이용해 상품을 담는 상황이 잘 연출되었다.

 

3. xml 설정

의 예제에서는 모두 Java 빈 객체 설정을 통해 확인해보았다. 만약 XML 파일에<bean> 태그를 이용한다면 다음과 같이 속성을 기재할 수 있다.

<!-- singleton 설정 --> 
<bean id="cart" class="패키지명.ShoppingCart" scope="singleton"/>

<!-- prototype 설정 -->
<bean id="cart" class="패키지명.ShoppingCart" scope="prototype"/>

XML 설정에 대한 별도의 실행 예제는 생략한다.

728x90
반응형
728x90
반응형

[Spring Framework] Spring Boot DI 핵심 가이드 | @Primary와 @Qualifier 제대로 이해하기

 

[Spring Framework] Spring Boot DI 핵심 가이드 | @Primary와 @Qualifier 제대로 이해하기

[Spring Framework] Spring Framework 의존성 주입(DI) 어노테이션 총정리 [Spring Framework] Spring Framework 의존성 주입(DI) 어노테이션 총정리[Spring Framework] Spring Boot에서 Dependency Injection 완벽 가이드 | 개념부터

crushed-taro.tistory.com

1. Collection

  • 같은 타입의 빈을 여러 개 주입 받고 싶다면Collection타입을 활용할 수 있다.

 

1. List 타입

List<Pokemon>타입의 객체를 의존성 주입 받는PokemonService래스를 선언한다.

@Service("pokemonServiceCollection")
public class PokemonService {
	
	/* 1. List 타입으로 주입 */
	private List<Pokemon> pokemonList;

	@Autowired
	public PokemonService(List<Pokemon> pokemonList) {
		this.pokemonList = pokemonList;
	}

	public void pokemonAttack() {
		pokemonList.forEach(Pokemon::attack);
	}
}

CharmanderPikachuSquirtlePokemonService 를 빈 스캐닝 할 수 있는 basePackages를 설정하여 스프링 컨테이너를 생성한다.

ApplicationContext context = new AnnotationConfigApplicationContext("project");

PokemonService pokemonService = context.getBean("pokemonServiceCollection", PokemonService.class);
		
pokemonService.pokemonAttack();

/*
파이리 불꽃 공격🔥
피카츄 백만볼트⚡
꼬부기 물대포 발사🌊
*/

bean 이름의 사전순으로 List에 추가 되어 모든 Pokemon 타입의 빈이 주입 된다.

 

2. Map 타입

Map<String, Pokemon>타입의 객체를 의존성 주입 받는PokemonService클래스를 선언한다.

@Service("pokemonServiceCollection")
public class PokemonService {
	
	/* 2. Map 타입으로 주입 */
	private Map<String, Pokemon> pokemonMap;
	
	@Autowired
	public PokemonService(Map<String, Pokemon> pokemonMap) {
		this.pokemonMap = pokemonMap;
	}
	
	public void pokemonAttack() {
      pokemonMap.forEach((k, v) -> {
          System.out.println("key : " + k);
          System.out.print("공격 : ");
          v.attack();
      });
  }
}

/*
key : charmander
공격 : 파이리 불꽃 공격🔥
key : pikachu
공격 : 피카츄 백만볼트⚡
key : squirtle
공격 : 꼬부기 물대포 발사🌊
*/

bean 이름의 사전순으로 Map에 추가 되어 모든 Pokemon 타입의 빈이 주입 된다.

 

2. Resource

@Resource 어노테이션은 자바에서 제공하는 기본 어노테이션이다. @Autowired와 같은 스프링 어노테이션과 다르게 
name 속성 값으로 의존성 주입을 할 수 있다.

해당 어노테이션은 사용하기 전 라이브러리 의존성 추가가 필요하므로 Maven Repository에서javax annoataion을 검색하여 build.gradle.kts파일에 아래와 같은 구문을 추가한다.

dependencies {
	implementation("javax.annotation:javax.annotation-api:1.3.2")
	...생략
}

 

1. 이름으로 주입

드로Pokemon 타입의 객체를 의존성 주입 받는 PokemonService 클래스를 선언한다. @Resource 어노테이션의 name속성에 주입할 빈 객체의 이름을 지정한다.

@Service("pokemonServiceResource")
public class PokemonService {
  
  /* pikachu 이름의 빈 지정 */
	@Resource(name = "pikachu")
	private Pokemon pokemon;

	public void pokemonAttack() {
		pokemon.attack();
	}
}

CharmanderPikachuSquirtlePokemonService 를 빈 스캐닝 할 수 있는 basePackages를 설정하여 스프링 컨테이너를 생성한다.

ApplicationContext context = new AnnotationConfigApplicationContext("project");

PokemonService pokemonService = context.getBean("pokemonServiceResour", PokemonService.class);
		
pokemonService.pokemonAttack();

/*
피카츄 백만볼트⚡
*/

 

2. 타입으로 주입

List<Pokemon> 타입으로 변경한 뒤 name 속성을 따로 기재하지 않고 동작시킬 수 있다. 기본적으로는 name속성을 통해 주입하지만 name 속성이 없을 경우 Type을 통해 의존성 주입한다.

@Service("pokemonServiceResource")
public class PokemonService {
  
  @Resource
	private List<Pokemon> pokemonList;

	public void pokemonAttack() {
		pokemonList.forEach(Pokemon::attack);
	}
}

/*
파이리 불꽃 공격🔥
피카츄 백만볼트⚡
꼬부기 물대포 발사🌊
*/

bean 이름의 사전순으로 List에 추가 되어 모든 Pokemon 타입의 빈이 주입 된다.

728x90
반응형
728x90
반응형

[Spring Framework] Spring Framework 의존성 주입(DI) 어노테이션 총정리

 

[Spring Framework] Spring Framework 의존성 주입(DI) 어노테이션 총정리

[Spring Framework] Spring Boot에서 Dependency Injection 완벽 가이드 | 개념부터 실습까지 [Spring Framework] Spring Boot에서 Dependency Injection 완벽 가이드 | 개념부터 실습까지[Spring Framework] Annotation-based Configuration

crushed-taro.tistory.com

1. DI Annotation

  • @Autowired 어노테이션은 가장 보편적으로 사용 되는 의존성 주입 Annotation이다. @Autowired 와 함께 사용하거나 또는 대체해서 사용할 수 있는 어노테이션을 학습한다.
  • 아래 코드는 테스트에 공통적으로 사용 할 Pokemon, Charmander, Pikachu, Squirtle 클래스이다.

 

  • Pokemon
public interface Pokemon {
	
	/* 공격하다 */
	void attack();
}
  • Charmander
@Component
public class Charmander implements Pokemon {

	@Override
	public void attack() {
		System.out.println("파이리 불꽃 공격🔥");
	}
}
  • Pikachu
@Component
public class Pikachu implements Pokemon {

	@Override
	public void attack() {
		System.out.println("피카츄 백만볼트⚡");
	}
}
  • Squirtle
@Component
public class Squirtle implements Pokemon {

	@Override
	public void attack() {
		System.out.println("꼬부기 물대포 발사🌊");
	}
}

 

1. @Primary

@Primary 어노테이션은 여러 개의 빈 객체 중에서 우선순위가 가장 높은 빈 객체를 지정하는 어노테이션이다.

생성자로 Pokemon 타입의 객체를 의존성 주입 받는 PokemonService클래스를 선언한다.

@Service("pokemonServicePrimary")
public class PokemonService {
	
	private Pokemon pokemon;
	
	@Autowired
	public PokemonService(Pokemon pokemon) {
		this.pokemon = pokemon;
	}
	
	public void pokemonAttack() {
		pokemon.attack();
	}

}

CharmanderPikachuSquirtlePokemonService 를 빈 스캐닝 할 수 있는 basePackages를 설정하여 스프링 컨테이너를 생성한다.

ApplicationContext context = new AnnotationConfigApplicationContext("project");

PokemonService pokemonService = context.getBean("pokemonServicePrimary", PokemonService.class);
		
pokemonService.pokemonAttack();

/*
Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: 
Error creating bean with name 'pokemonServicePrimary' defined in file 파일 경로 : 
Unsatisfied dependency expressed through constructor parameter 0; 
nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: 
No qualifying bean of type 'project.common.Pokemon' available: 
expected single matching bean but found 3: charmander,pikachu,squirtle
...생략
*/

스프링 컨테이너 내부에 Pokemon 타입의 빈 객체가 charmander,pikachu,squirtle 3개가 있어 1개의 객체를 PokemonService의 생성자로 전달할 수 없어 오류가 발생했음을 확인할 수 있다.

 

Charmander,Pikachu,Squirtle중에서Charmander빈 객체를 우선적으로 주입받도록@Primary어노테이션을 설정한다.

@Component
@Primary
public class Charmander implements Pokemon {

    @Override
    public void attack() {
        System.out.println("파이리 불꽃 공격🔥");
    }
}

/*
파이리 불꽃 공격🔥
*/

@Primary 어노테이션을 설정하면 @Autowired로 동일한 타입의 여러 빈을 찾게 되는 경우 자동으로 연결 우선 시 할 타입으로 설정 된다.

동일한 타입의 클래스 중 한 개만 @Primary 어노테이션을 사용할 수 있다.

Charmander 빈 객체에 @Primary 어노테이션이 설정되어 있으므로, PokemonService의 생성자로 Pokemon 객체를 주입받으면 Charmander 빈 객체가 우선적으로 주입된다.

 

2. @Qualifier

@Qualifierb어노테이션은 여러 개의 빈 객체 중에서 특정 빈 객체를 이름으로 지정하는 어노테이션이다. 

 

1. 필드 주입

드로Pokemon 타입의 객체를 의존성 주입 받는 PokemonService 클래스를 선언한다. @Autowired 어노테이션과 함께 @Qualifier 어노테이션을 사용하여 빈 이름을 통해 주입할 빈 객체를 지정한다.

@Service("pokemonServiceQualifier")
public class PokemonService {
	
  /* @Qualifier 어노테이션을 사용하여 pikachu 빈 객체를 지정한다. */
	@Autowired
	@Qualifier("pikachu")
	private Pokemon pokemon;
	
	public void pokemonAttack() {
		pokemon.attack();
	
}

CharmanderPikachuSquirtlePokemonService 를 빈 스캐닝 할 수 있는 basePackages를 설정하여 스프링 컨테이너를 생성한다.

ApplicationContext context = new AnnotationConfigApplicationContext("project");

PokemonService pokemonService = context.getBean("pokemonServiceQualifier", PokemonService.class);
		
pokemonService.pokemonAttack();

/*
피카츄 백만볼트⚡
*/

@Primary 어노테이션과 @Qualifier 어노테이션이 함께 쓰였을 때 @Qualifier 우선한다는 것도 결과를 통해 확인할 수 있다.

 

2. 생성자 주입

생성자 주입의 경우 @Qualifier 어노테이션은 메소드의 파라미터 앞에 기재한다. 역시 빈 이름을 통해 주입할 빈 객체를 지정한다.

@Service("pokemonServiceQualifier")
public class PokemonService {
	
	private Pokemon pokemon;

  /* @Qualifier 어노테이션을 사용하여 squirtle 빈 객체를 지정한다. */
	@Autowired
	public PokemonService(@Qualifier("squirtle") Pokemon pokemon) {
		this.pokemon = pokemon;
	}
	
	public void pokemonAttack() {
		pokemon.attack();
	}
}

/*
꼬부기 물대포 발사🌊
*/
728x90
반응형
728x90
반응형

[Spring Framework] Spring Boot에서 Dependency Injection 완벽 가이드 | 개념부터 실습까지

 

[Spring Framework] Spring Boot에서 Dependency Injection 완벽 가이드 | 개념부터 실습까지

[Spring Framework] Annotation-based Configuration이란? Spring 설정을 더 간결하게! [Spring Framework] Annotation-based Configuration이란? Spring 설정을 더 간결하게![Spring Framework] Spring IoC 컨테이너 사용법 완벽 정리 | 실

crushed-taro.tistory.com

1. DI Annotation (@Autowired)

@Autowired 어노테이션은 Type을 통한 DI를 할 때 사용한다. 스프링 컨테이너가 알아서 해당 타입의 Bean을 찾아서 주입해준다.

아래 코드는 테스트에 공통적으로 사용 할 BookDTOBookDAOBookDAOImpl 클래스이다.

 

  • BookDTO
@Data
@AllArgsConstructor
public class BookDTO {

    private int sequence;        //도서번호
    private int isbn;            //isbn
    private String title;        //제목
    private String author;       //저자
    private String publisher;    //출판사
    private Date createdDate;    //출판일

}

 

  • BookDAO
public interface BookDAO {

    /* 도서 목록 전체 조회 */
    List<BookDTO> selectBookList();

    /* 도서 번호로 도서 조회 */
    BookDTO selectOneBook(int sequence);
}

 

  • BookDAOImpl
/* @Repository : @Component의 세분화 어노테이션의 한 종류로 DAO 타입의 객체에 사용한다. */
@Repository("bookDAO")
public class BookDAOImpl implements BookDAO {

    private Map<Integer, BookDTO> bookList;

    public BookDAOImpl() {
        bookList = new HashMap<>();
        bookList.put(1, new BookDTO(1, 123456, "자바의 정석", "남궁성", "도우출판", new Date()));
        bookList.put(2, 
					new BookDTO(2, 654321, "칭찬은 고래도 춤추게 한다", "고래", "고래출판", new Date()));
    }

    @Override
    public List<BookDTO> selectBookList() {
        return new ArrayList<>(bookList.values());
    }

    @Override
    public BookDTO selectOneBook(int sequence) {
        return bookList.get(sequence);
    }
}

 

1. 필드(field) 주입

/* @Service : @Component의 세분화 어노테이션의 한 종류로 Service 계층에서 사용한다. */
@Service("bookServiceField")
public class BookService {

    /* BookDAO 타입의 빈 객체를 이 프로퍼티에 자동으로 주입해준다. */
    @Autowired
    private BookDAO bookDAO;

		/* 도서 목록 전체 조회 */
    public List<BookDTO> selectAllBooks(){

        return bookDAO.selectBookList();
    }

		/* 도서 번호로 도서 조회 */
    public BookDTO searchBookBySequence(int sequence) {

        return bookDAO.selectOneBook(sequence);
    }
}

private BookDAO bookDAO = new BookDAOImpl(); 와 같이 필드를 선언한다면 BookService 클래스는 BookDAOImpl 클래스의 변경에 직접적으로 영향을 받는 강한 결합을 가지게 된다. 객체간의 결합을 느슨하게 하기 위해 new BookDAOImpl() 와 같은 직접적으로 객체를 생성하는 생성자 구문을 제거하고 필드에 @Autowired 어노테이션을 작성할 수 있다. 그러면 스프링 컨테이너는 BookService 빈 객체 생성 시 BookDAO 타입의 빈 객체를 찾아 의존성을 주입해준다.

 

스프링 컨테이너를 생성하여 @Repository@Service 등의 어노테이션이 작성 된 클래스가 빈 스캐닝을 통해 잘 등록 되었는지, 또한 객체의 의존 관계에 따라 @Autowired어노테이션을 통해 의존성 주입이 되었는지를 테스트한다.

/* AnnotationConfigApplicationContext 생성자에 basePackages 문자열을 전달하며 ApplicationContext 생성한다. */
ApplicationContext context = new AnnotationConfigApplicationContext("project");

BookService bookService = context.getBean("bookServiceField", BookService.class);

/* 전체 도서 목록 조회 후 출력 확인 */
bookService.selectAllBooks().forEach(System.out::println);

/* 도서번호로 검색 후 출력 확인*/
System.out.println(bookService.searchBookBySequence(1));
System.out.println(bookService.searchBookBySequence(2));

/*
BookDTO(sequence=1, isbn=123456, title=자바의 정석, author=남궁성, publisher=도우출판, 
	createdDate=Sun May 28 20:19:12 KST 2023)
BookDTO(sequence=2, isbn=654321, title=칭찬은 고래도 춤추게 한다, author=고래, publisher=고래출판, 
	createdDate=Sun May 28 20:19:12 KST 2023)
BookDTO(sequence=1, isbn=123456, title=자바의 정석, author=남궁성, publisher=도우출판, 
	createdDate=Sun May 28 20:19:12 KST 2023)
BookDTO(sequence=2, isbn=654321, title=칭찬은 고래도 춤추게 한다, author=고래, publisher=고래출판, 
	createdDate=Sun May 28 20:19:12 KST 2023)
*/

 

2. 생성자(constructor) 주입

/* @Service : @Component의 세분화 어노테이션의 한 종류로 Service 계층에서 사용한다. */
@Service("bookServiceConstructor")
public class BookService {

    private final BookDAO bookDAO;

		/* BookDAO 타입의 빈 객체를 생성자에 자동으로 주입해준다. */
    @Autowired
    public BookService(BookDAO bookDAO) {
        this.bookDAO = bookDAO;
    }

    public List<BookDTO> selectAllBooks(){

        return bookDAO.selectBookList();
    }

    public BookDTO searchBookBySequence(int sequence) {

        return bookDAO.selectOneBook(sequence);
    }

}

생성자에도 @Autowired 어노테이션을 작성할 수 있다. 그러면 스프링 컨테이너는 BookService 빈 객체 생성 시 BookDAO 타입의 빈 객체를 찾아 의존성을 주입해준다.

Spring 4.3 버전 이후로는 생성자가 한 개 뿐이라면 @Autowired 어노테이션을 생략해도 자동으로 생성자 주입이 동작한다. 단, 생성자가 1개 이상일 경우에는 명시적으로 작성을 해주어야 한다. 위의 코드에 기본 생성자를 추가로 작성하고 매개변수 생성자에 @Autowired 어노테이션을 생략하게 되면 생성자 주입이 동작하지 않아 오류가 발생한다.

 

생성자 주입의 장점

  • 객체가 생성 될 때 모든 의존성이 주입 되므로 의존성을 보장할 수 있다.
    • 필드 주입/세터 주입은 의존성이 있는 객체가 생성되지 않아도 객체 생성은 가능하여 메소드가 호출 되면(런타임) 오류가 발생한다.
    • 생성자 주입은 의존성이 있는 객체가 생성되지 않으면 객체 생성이 불가능하여 어플리케이션 실행 시점에 오류가 발생한다.
  • 객체의 불변성을 보장할 수 있다.
    • 필드에 final 키워드를 사용 할 수 있고 객체 생성 이후 의존성을 변경할 수 없어 안정성이 보장 된다.
  • 코드 가독성이 좋다.
    • 해당 객체가 어떤 의존성을 가지고 있는지 명확히 알 수 있다.
  • DI 컨테이너와의 결합도가 낮기 때문에 테스트 하기 좋다.
    • 스프링 컨테이너 없이 테스트를 할 수 있다.

 

프링 컨테이너를 생성하여@Repository@Service 등의 어노테이션이 작성 된 클래스가 빈 스캐닝을 통해 잘 등록 되었는지, 또한 객체의 의존 관계에 따라 @Autowired어노테이션을 통해 의존성 주입이 되었는지를 테스트한다.

/* AnnotationConfigApplicationContext 생성자에 basePackages 문자열을 전달하며 ApplicationContext 생성한다. */
ApplicationContext context = new AnnotationConfigApplicationContext("project");

BookService bookService = context.getBean("bookServiceConstructor", BookService.class);

/* 전체 도서 목록 조회 후 출력 확인 */
bookService.selectAllBooks().forEach(System.out::println);

/* 도서번호로 검색 후 출력 확인*/
System.out.println(bookService.searchBookBySequence(1));
System.out.println(bookService.searchBookBySequence(2));

/*
BookDTO(sequence=1, isbn=123456, title=자바의 정석, author=남궁성, publisher=도우출판, 
	createdDate=Sun May 28 20:19:12 KST 2023)
BookDTO(sequence=2, isbn=654321, title=칭찬은 고래도 춤추게 한다, author=고래, publisher=고래출판, 
	createdDate=Sun May 28 20:19:12 KST 2023)
BookDTO(sequence=1, isbn=123456, title=자바의 정석, author=남궁성, publisher=도우출판, 
	createdDate=Sun May 28 20:19:12 KST 2023)
BookDTO(sequence=2, isbn=654321, title=칭찬은 고래도 춤추게 한다, author=고래, publisher=고래출판, 
	createdDate=Sun May 28 20:19:12 KST 2023)
*/

 

3. 세터(setter) 주입

/* @Service : @Component의 세분화 어노테이션의 한 종류로 Service 계층에서 사용한다. */
@Service("bookServiceSetter")
public class BookService {

    private BookDAO bookDAO;

    /* BookDAO 타입의 빈 객체를 setter에 자동으로 주입해준다. */
    @Autowired
    public void setBookDAO(BookDAO bookDAO) {
        this.bookDAO = bookDAO;
    }

    public List<BookDTO> selectAllBooks(){

        return bookDAO.selectBookList();
    }

    public BookDTO searchBookBySequence(int sequence) {

        return bookDAO.selectOneBook(sequence);
    }
}

setter 메소드에도 @Autowired 어노테이션을 작성할 수 있다. 그러면 스프링 컨테이너는 BookService 빈 객체 생성 시 BookDAO 타입의 빈 객체를 찾아 의존성을 주입해준다. 

 

스프링 컨테이너를 생성하여 @Repository@Service 등의 어노테이션이 작성 된 클래스가 빈 스캐닝을 통해 잘 등록 되었는지, 또한 객체의 의존 관계에 따라 @Autowired어노테이션을 통해 의존성 주입이 되었는지를 테스트한다.

/* AnnotationConfigApplicationContext 생성자에 basePackages 문자열을 전달하며 ApplicationContext 생성한다. */
ApplicationContext context = new AnnotationConfigApplicationContext("project");

BookService bookService = context.getBean("bookServiceSetter", BookService.class);

/* 전체 도서 목록 조회 후 출력 확인 */
bookService.selectAllBooks().forEach(System.out::println);

/* 도서번호로 검색 후 출력 확인*/
System.out.println(bookService.searchBookBySequence(1));
System.out.println(bookService.searchBookBySequence(2));

/*
BookDTO(sequence=1, isbn=123456, title=자바의 정석, author=남궁성, publisher=도우출판, 
	createdDate=Sun May 28 20:19:12 KST 2023)
BookDTO(sequence=2, isbn=654321, title=칭찬은 고래도 춤추게 한다, author=고래, publisher=고래출판, 
	createdDate=Sun May 28 20:19:12 KST 2023)
BookDTO(sequence=1, isbn=123456, title=자바의 정석, author=남궁성, publisher=도우출판, 
	createdDate=Sun May 28 20:19:12 KST 2023)
BookDTO(sequence=2, isbn=654321, title=칭찬은 고래도 춤추게 한다, author=고래, publisher=고래출판, 
	createdDate=Sun May 28 20:19:12 KST 2023)
*/
728x90
반응형
728x90
반응형

[Spring Framework] Spring IoC 컨테이너 사용법 완벽 정리 | 실전 예제로 이해하는 DI

 

[Spring Framework] Spring IoC 컨테이너 사용법 완벽 정리 | 실전 예제로 이해하는 DI

[Spring Framework] 스프링 IoC 컨테이너 이해하기 | 정의 [Spring Framework] 스프링 IoC 컨테이너 이해하기 | 정의[Spring Framework] Spring Framework란? 개요와 핵심 구성 모듈 정리 [Spring Framework] Spring Framework란? 개

crushed-taro.tistory.com

1. IoC Container

1. Annotation-based Configuration

1. @ComponentScan이란?

  • base package로 설정 된 하위 경로에 특정 어노테이션을 가지고 있는 클래스를 bean으로 등록하는 기능이다.
  • @Component 어노테이션이 작성 된 클래스를 인식하여 bean으로 등록한다.
  • 특수 목적에 따라 세부 기능을 제공하는 @Controller, @Service, @Repository, @Configuration 등을 사용한다.
어노테이션 설명
@Component 객체를 나타내는 일반적인 타입으로<bean> 태그와 동일한 역할
@Controller 프리젠테이션 레이어. 웹 어플리케이션에서 View에서 전달된 웹 요청과 응답을 처리하는 클래스
EX) Controller Class
@Service 서비스 레이어, 비즈니스 로직을 가진 클래스
EX) Service Class
@Repository 퍼시스턴스(persistence) 레이어, 영속성을 가지는 속성(파일, 데이터베이스)을 가진 클래스
EX) Data Access Object Class
@Configuration 빈을 등록하는 설정 클래스

Spring 사진 1

 

2. @Component 어노테이션으로 자동 빈 등록하기

@Component
public class MemberDAO {

    private final Map<Integer, MemberDTO> memberMap;

    public MemberDAO() {
        memberMap = new HashMap<>();

        memberMap.put(1, new MemberDTO(1, "user01", "pass01", "홍길동"));
        memberMap.put(2, new MemberDTO(2, "user02", "pass02", "유관순"));
    }

    /* 매개변수로 전달 받은 회원 번호를 map에서 조회 후 회원 정보를 리턴하는 메소드 */
    public MemberDTO selectMember(int sequence) {
        return memberMap.get(sequence);
    }

    /* 매개변수를 전달 받은 회원 정보를 map에 추가하고 성공 실패 여부를 boolean으로 리턴하는 메소드 */
    public boolean insertMember(MemberDTO newMember) {

        int before = memberMap.size();

        memberMap.put(newMember.getSequence(), newMember);

        int after = memberMap.size();

        return after > before;
    }
}

bean으로 등록하고자 하는 클래스 위에 @Component 어노테이션을 기재하면 스프링 컨테이너 구동 시 빈으로 자동 등록된다.

  • 이름을 별도로 지정하지 않으면 클래스명의 첫 문자를 소문자로 변경하여 bean의 id로 자동 인식한다.
  • @Component("myName") 또는 @Component(value="myName") 의 형식으로 bean의 id를 설정할 수 있다.

 

3. @ComponentScan 어노테이션으로 base packages 설정하기

@ComponentScan(basePackages="project")
public class ContextConfiguration {}

@ComponentScan어노테이션의 basePackagese 속성에 입력한 패키지가 빈 스캐닝의 범위가 된다.

 

스프링 컨테이너에 빈 스캐닝을 통해bean이 자동으로 등록 되고 생성 되었는지 확인한다.

ApplicationContext context 
	= new AnnotationConfigApplicationContext(ContextConfiguration.class);

/* getBeanDefinitionNames : 스프링 컨테이너에서 생성 된 bean들의 이름을 배열로 반환한다. */
String[] beanNames = context.getBeanDefinitionNames();
for(String beanName : beanNames) {
		System.out.println("beanName : " + beanName);
}

/*
...생략
beanName : contextConfiguration
beanName : memberDAO
*/

 

4. excludeFilters

@ComponentScan 의 excludeFilters속성을 이용해서 일부 컴포넌트를 빈 스캐닝에서 제외할 수 있다.

  • Type으로 설정하는 방법
@ComponentScan(basePackages="project",
	excludeFilters={
		@ComponentScan.Filter(type=FilterType.ASSIGNABLE_TYPE, 
		classes={MemberDAO.class})
})
public class ContextConfiguration {}

 

  • Annotation 종류로 설정하는 방법
@ComponentScan(basePackages="project",
	excludeFilters={
		@ComponentScan.Filter(type=FilterType.ANNOTATION, 
		classes={org.springframework.stereotype.Component.class})
})
public class ContextConfiguration {}

 

  • 표현식으로 설정하는 방법
@ComponentScan(basePackages="project",
	excludeFilters={
		@ComponentScan.Filter(type=FilterType.REGEX, 
		pattern={"annotationconfig.java.*"})
})
public class ContextConfiguration {}

 

빈 스캐닝에MemberDAO클래스가 제외되어 아래 코드를 실행하면 해당 이름을 가진 빈이 없다는 오류가 발생한다.

ApplicationContext context = new AnnotationConfigApplicationContext(ContextConfiguration.class);

String[] beanNames = context.getBeanDefinitionNames();
for(String beanName : beanNames) {
		System.out.println("beanName : " + beanName);
}

MemberDAO memberDAO = context.getBean("memberDAO", MemberDAO.class);

/*
...생략
beanName : contextConfiguration
Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: 
No bean named 'memberDAO' available
*/

 

5. includeFilters

@ComponentScan 의 includeFilters성을 이용해서 일부 컴포넌트를 빈 스캐닝에 포함 시킬 수 있다.

@ComponentScan(basePackages="project",
	useDefaultFilters=false,
	includeFilters={@ComponentScan.Filter(type=FilterType.ASSIGNABLE_TYPE, 
		classes= {MemberDAO.class})
})
public class ContextConfiguration {}

useDefaultFilters 속성의 기본 값은 true로 @Component 종류의 어노테이션을 자동으로 등록 처리 해준다. 해당 속성을 false로 변경하면 컴포넌트 스캔이 일어나지 않게 된다.

별도로 includeFilters 속성을 정의해 컴포넌트 스캔이 발생하도록 한다.

excludeFilters에서 설정하는 방식과 동일하므로 종류별 예시는 생략한다.

 

아래 코드를 실행하면 빈 스캐닝에서MemberDAO클래스가 포함되어 해당 이름을 가진 빈을 확인할 수 있다.

ApplicationContext context 
	= new AnnotationConfigApplicationContext(ContextConfiguration.class);

String[] beanNames = context.getBeanDefinitionNames();
for(String beanName : beanNames) {
		System.out.println("beanName : " + beanName);
}

/*
...생략
beanName : contextConfiguration
beanName : memberDAO
*/

 

6. XML에서 Component Scan 설정

아래와 같이 XML 설정 파일에서 Component Scan의base package를 설정할 수도 있다.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
													 http://www.springframework.org/schema/beans/spring-beans.xsd 
													 http://www.springframework.org/schema/context 
													 https://www.springframework.org/schema/context/spring-context.xsd">

	<context:component-scan base-package="project"/>

</beans>

<context:component-scan> 태그는 context 네임스페이스의 기능이기 때문에 context: 접두어가 필요하다.

또한 XML 설정 파일의 기본 네임스페이스가 beans로 설정 되어 있기 때문에 context 네임스페이스 추가가 필요하다.

 

base package@Component 어노테이션을 작성한 MemberDAOclass를 배치해 두고 아래와 같은 코드를 실행하면 빈이 등록 되어있음을 확인할 수 있다.

ApplicationContext context 
	= new GenericXmlApplicationContext("section03/javaannotation/spring-context.xml");

String[] beanNames = context.getBeanDefinitionNames();
for(String beanName : beanNames) {
		System.out.println("beanName : " + beanName);
}

/*
beanName : memberDAO
...생략
*/

 

아래와 같이 <context:component-scan> 태그 내부에 <exclude-filter> 또는 <include-filter> 태그를 정의할 수 있다.

<include-filter> 의 경우 동일한 방식으로 정의하므로 생략하였다.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
													 http://www.springframework.org/schema/beans/spring-beans.xsd 
													 http://www.springframework.org/schema/context 
													 https://www.springframework.org/schema/context/spring-context.xsd">

	<context:component-scan base-package="project">
        <context:exclude-filter type="assignable" expression="common.MemberDAO"/>
  </context:component-scan>

</beans>

 

스캐닝에서MemberDAO클래스가 제외되어 아래 코드를 실행하면 해당 이름을 가진 빈이 없다는 오류가 발생한다.

ApplicationContext context 
	= new GenericXmlApplicationContext("section03/javaannotation/spring-context.xml");

String[] beanNames = context.getBeanDefinitionNames();
for(String beanName : beanNames) {
		System.out.println("beanName : " + beanName);
}

MemberDAO memberDAO = context.getBean("memberDAO", MemberDAO.class);

/*
...생략
Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException
: No bean named 'memberDAO' available
*/

 

정리

  • XML 설정은 전통적으로 사용하던 방식으로 최근에는 Java 설정이 선호된다.
  • 개발자가 직접 컨트롤 가능한 Class의 경우, @Component를 클래스에 정의하여 빈 스캐닝을 통한 자동 빈 등록을 한다.
  • 개발자가 직접 제어할 수 없는 외부 라이브러리는 @Bean을 메소드에 사용하여 수동 빈 등록을 한다.
    • 다형성을 활용하고 싶은 경우에도 @Bean을 사용할 수 있다.
 

Java-based Container Configuration :: Spring Framework

This section covers how to use annotations in your Java code to configure the Spring container.

docs.spring.io

728x90
반응형
728x90
반응형

[Spring Framework] 스프링 IoC 컨테이너 이해하기 | 정의

 

[Spring Framework] 스프링 IoC 컨테이너 이해하기 | 정의

[Spring Framework] Spring Framework란? 개요와 핵심 구성 모듈 정리 [Spring Framework] Spring Framework란? 개요와 핵심 구성 모듈 정리[Servlet] Servlet에서 Thread가 어떻게 작동하는가? 웹 개발자를 위한 완벽 가이드

crushed-taro.tistory.com

1. IoC Container 사용하기

  • 아래 코드는 테스트에 공통적으로 사용할MemberDTO클래스이다.
@Data
@AllArgsConstructor
public class MemberDTO {

    private int sequence;     //회원번호
    private String id;        //아이디
    private String pwd;       //비밀번호
    private String name;      //이름

}

 

1. XML - based Configuration

1. GenericApplicationContext

GenericXmlApplicationContext Spring IoC Container 중 하나이다. XML 형태의 Configuration Metadata를 사용하여 Bean을 생성한다.

/* GenericXmlApplicationContext 클래스를 사용하여 ApplicationContext를 생성한다. 
 * 생성자에 XML 설정 메타 정보를 인자로 전달한다. */
ApplicationContext context 
	= new GenericXmlApplicationContext("spring-context.xml"
		/*XML Configuration Metadata 파일 경로*/);

 

2. XML 기반 Configuration Metadata

XML 형태의 Configuration Metadata 파일은 다음과 같이 작성할 수 있다. 다음 예제에서는MemberDTO클래스의 객체를 Bean으로 등록하고 있다.

<?xml version="1.0" encoding="UTF-8"?>
<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="member" class="MemberDTO">
				<!-- int 타입의 첫 번째 파라미터에 1 값을 전달 -->
        <constructor-arg index="0" value="1"/>
	      <!-- String 타입의 id 파라미터에 "user01" 값을 전달 -->
        <constructor-arg name="id" value="user01"/>
        <!-- String 타입의 세 번째 파라미터에 "pass01" 값을 전달 -->
        <constructor-arg index="2"><value>pass01</value></constructor-arg>
				<!-- String 타입의 name 파라미터에 "홍길동" 값을 전달 -->
        <constructor-arg name="name"><value>홍길동</value></constructor-arg>
    </bean>

</beans>

 

  • <beans>그는 Bean 설정 정보를 담고 있는 메타데이터를 정의하는 태그이다.
    • xmlns : XML Namespace를 정의
    • xmlns:xsi : XML Schema Instance Namespace를 정의
    • xsi:schemaLocation : XML Schema Location을 정의
    • XML Schema는 XML 문서의 구조와 유효성을 검사하기 위한 언어로, XML Schema Definition(XSD) 파일을 통해 정의
  • <beans> 태그 내부에 <bean>그를 사용해 하나의 bean 설정 정보를 작성할 수 있다.
    • id : Bean의 이름을 정의
    • class : 객체의 클래스를 지정
  • <constructor-arg> 태그는 생성자를 호출할 때 전달할 인자를 정의한다. 만약 <bean> 태그 내부에 아무 것도 작성하지 않으면 기본 생성자를 사용한다는 의미이다.
    • index : 메서드의 파라미터 순서(index)로 전달
    • name : 파라미터 이름으로 전달
    • value : 파라미터 값으로 전달

 

3. GenericApplicationContext(스프링 컨테이너) 생성 테스트

GenericApplicationContext에 bean등록 되고 생성 되었는지 확인한다.

  • bean의 id를 이용해서 bean을 가져오는 방법
MemberDTO member = (MemberDTO) context.getBean("member");
System.out.println(member);

//MemberDTO(sequence=1, id=user01, pwd=pass01, name=홍길동)

 

  • bean의 클래스 메타 정보를 전달하여 가져오는 방법
MemberDTO member = context.getBean(MemberDTO.class);
System.out.println(member);

//MemberDTO(sequence=1, id=user01, pwd=pass01, name=홍길동)

 

  • bean의 id와 클래스 메타 정보를 전달하여 가져오는 방법
MemberDTO member = (MemberDTO) context.getBean("member");
System.out.println(member);

//MemberDTO(sequence=1, id=user01, pwd=pass01, name=홍길동)

 

2. Java-based Configuration

1. AnnotationConfigApplicationContext

AnnotationConfigApplicationContext는 Spring IoC Container 중 하나이다. Java Configuration 형태의 Configuration Metadata를 사용하여 Bean을 생성한다.

/* AnnotationConfigApplicationContext클래스를 사용하여 ApplicationContext를 생성한다. 
 * 생성자에 @Configuration 어노테이션이 달린 설정 클래스의 메타 정보를 전달한다. */
ApplicationContext context 
	= new AnnotationConfigApplicationContext(ContextConfiguration.class);

 

2. Java 기반 Configuration Metadata

Java Configuration 형태의 Configuration Metadata 파일은 다음과 같이 작성할 수 있다. 다음 예제에서는MemberDTO래스의 객체를 Bean으로 등록하고 있다.

@Configuration
public class ContextConfiguration {

    @Bean(name="member")
    public MemberDTO getMember() {

        return new MemberDTO(1, "user01", "pass01", "홍길동");
    }

}

 

@Configuration 어노테이션은 해당 클래스가 빈을 생성하는 클래스임을 표기한다.

 

  • @Bean 어노테이션은 해당 메소드의 반환 값을 스프링 컨테이너에 빈으로 등록한다는 의미이다.
    • 이름을 별도로 지정하지 않으면 메소드 이름을 bean의 id로 자동 인식한다.
    • @Bean("myName") 또는 @Bean(name="myName") 의 형식으로 bean의 id를 설정할 수 있다.

 

3. AnnotationConfigApplicationContext(스프링 컨테이너) 생성 테스트

AnnotationConfigApplicationContext에 bean 이 등록 되고 생성 되었는지 확인한다.

MemberDTO member = context.getBean("member", MemberDTO.class);
System.out.println(member);

//MemberDTO(sequence=1, id=user01, pwd=pass01, name=홍길동)
728x90
반응형
728x90
반응형

[Spring Framework] Spring Framework란? 개요와 핵심 구성 모듈 정리

 

[Spring Framework] Spring Framework란? 개요와 핵심 구성 모듈 정리

[Servlet] Servlet에서 Thread가 어떻게 작동하는가? 웹 개발자를 위한 완벽 가이드 [Servlet] Servlet에서 Thread가 어떻게 작동하는가? 웹 개발자를 위한 완벽 가이드[Servlet] 자바 서블릿과 네트워크 | 요청

crushed-taro.tistory.com

1. IoC Container

1. IoC & IoC Container 정의

1. IoC(Inversion of Control)란?

제어의 역전(IoC, Inversion of Control)은 일반적인 프로그래밍에서, 프로그램의 제어 흐름 구조가 뒤바뀌는 것을 의미한다.
  • ⇒ 객체의생성과 관리, 객체 간의 의존성 처리을 프레임워크에서 대신 처리해주는 것이 IoC의 대표적인 예이다.

 

2. IoC Container란?

IoC Container는 IoC를 구현한 구체적인 프레임워크를 말한다. IoC Container를 사용하면 객체의 생성, 초기화, 의존성 처리 등을 자동으로 수행할 수 있다. 
  • ⇒ 대표적인 IoC Container로는 Spring Framework의ApplicationContext가 있다.

 

Spring 사진 1

 

2. Spring IoC Container

1. Bean이란?

Bean은 Spring IoC Container에서 관리되는 객체를 말한다. 
  • 스프링은 Bean을생성하고, 초기화하고, 의존성 주입하고, 제거하는 등의 일을 IoC Container를 통해 자동으로 처리할 수 있다.

 

2. Bean Factory란?

BeanFactory는 Spring IoC Container의 가장 기본적인 형태로, Bean의 생성, 초기화, 연결, 제거 등의 라이프사이클을 관리한다. 
  • 이를 위해Configuration Metadata를 사용한다.

 

3. Configuration Metadata란?

BeanFactory가 IoC를 적용하기 위해 사용하는 설정 정보다.
  • ⇒ 설정 메타 정보는 IoC 컨테이너에 의해 관리 되는 Bean 객체를 생성하고 구성할 때 사용 된다.

Spring 사진 2

 

4. Application Context란?

BeanFactory를 확장한 IoC 컨테이너로 Bean을 등록하고 관리하는 기능은 BeanFactory와 동일하지만, 스프링이 제공하는 각종 부가 기능을 추가로 제공한다.

 

Spring 사진 3

 

  • ListableBeanFactory : BeanFactory가 제공하는 모든 기능을 포함한다.
  • ApplicationEventPublisher : 이벤트 처리(Event Handling) 기능을 제공한다.
  • MessageSource : 국제화(i18n) 를 지원하는 메세지를 해결하는 부가 기능을 제공한다.
  • ResourceLoader : 리소스 핸들링(Resource Handling) 기능을 제공한다.
  • GenericXmlApplicationContext : ApplicationContext를 구현한 클래스. XML MetaData Configuration을 읽어 컨테이너 역할을 수행한다.
  • AnnotationConfigApplicationContext : ApplicationContext를 구현한 클래스. Java MetaData Configuration을 읽어 컨테이너 역할을 수행한다.
728x90
반응형
728x90
반응형

1. Spring Framework

스프링 프레임워크(Spring Framework)는 자바 플랫폼을 위한 오픈 소스 애플리케이션 프레임워크로, 일반적으로 스프링(Spring)이라고도 불린다. 동적인 웹 사티으 개발에 필요한 다양한 서비스를 제공하고, 대한민국 공공기관 웹 서비스 개발 시 권장되는 전자정부 표준프레임워크의 기반 기술로도 쓰인다.

2. 특징

  1. 오픈소스
    • 활발한 커뮤니티와 다양한 피드백을 통해 지속적으로 발전하고 있다.
  2. Java 엔터프라이즈 애플리케이션 개발이 쉽다
    • 다양한 아키텍처를 유연하게 구성할 수 있고, 엔터프라이즈 환경에 필요한 기능을 모두 제공한다.
  3. 프레임워크의 기본 원칙을 지킨다
    • 강력한 이전 버전과의 호환성 유지
    • 유연하고 비독선적 : 특정 방식을 강요하지 않고, 다양한 애플리케이션 요구사항을 지원한다.
    • 선택의 자유 : 디자인 결정을 유연하게 내릴 수 있으며, 예를 들어 구성만 변경해서도 지속성 공급자를 전환할 수 있다.
    • 깨끗한 코드 구조와 명확한 문서화 : 순환 종속성이 없는 구조와 의미 있는 최신 javadoc을 제공한다.
728x90
반응형

'이것 저것 개발 공부 > JAVA' 카테고리의 다른 글

[Java] 변수  (0) 2025.02.26
[JAVA] Math 클래스  (1) 2025.02.13
[JAVA] StringBuilder / StringBuffer 클래스  (1) 2025.02.13
[JAVA] Wrapper 클래스  (0) 2025.02.13
[JAVA] Object 클래스  (0) 2025.02.13
728x90
반응형

MyBatis

1. MyBatis

 - 데이터의 입력, 조회, 수정, 삭제(CRUD)를 보다편하게 할 수 있도록 xml로 구조화한 Mapper설정 파일을 통해 JDBC를 구현한 영속성 프레임워크.

 - 기존에 JDBC를 통해 구현했던 상당 부분의 코드, 파라미터 설정 및 결과 mapping을 xml 설정으로 쉽게 구현할 수 있게 함.

 - MyBatis는 기존 iBatis의 한계점인 Dynamic query(동적 쿼리)와 Annotation 처리를 보강하여 더 나을 기능을 제공함.

 - iBatis는 현재 비활성화 상태이며, 기존에 iBatis로 만들어진 애플리케이션의 지원을 위한 라이브러리만 제공하고 있음.

 

2. MyBatis Configuration by JAVA

 - Environment : 데이터베이스 접속에 관한 환경 설정 정보를 가진 객체로, MyBatis 환경 설정 객체를 생성하는 데 사용함.

 - Configuration : 환경 설정 정보 Environment 객체를 가지고 생성한 MyBatis 설정 객체로, DB 접속 관련 정보, mapper 등록, 별칭 등록 등 MyBatis 전역 설정 정보를 담고 있음.

 - SqlSessionFactory

  - SqlSession 객체를 생성하기 위한 팩토리 역할을 수행하는 인터페이스.

  - 애플리케이션이 실행되는 동안 여러 차례 빌드하지 않도록, 싱글톤 패턴 등을 이용하여 어플리케이션 스코프로 사용하는 것이 좋음.

 - SqlSessionFactoryBuilder

  - SqlSessionFactory 인터페이스 타입의 하위 구현 객체를 생성하기 위한 빌드 역할을 수행함.

  - build() 메서드는 설정 정보를 담고 있는 Configuration 타입의 객체 혹은 외부 설정 파일과 연결된 스트림을 매개변수로 전달하면 SqlSessionFactory 인터페이스 타입의 객체를 반환함.

 - MyBatis 인스턴스들의 Scope와 Life-Cycle

  - SqlSessionFactoryBuilder : 로컬 스코프 -> 메서드 내부 또는 초기화 블럭 내부

   - 일반적으로 한 번 SqlSessionFactory를 빌드하고 나면 더 이상 필요하지 않음. (일회성)

   - 따라서 짧은 라이프 사이클을 가지게 되며 더 이상 필요하지 않게 되면 즉시 가비지 컬렉터의 대상이 될 수 있음.

   - 일반적으로 데이터베이스 작업(CRUD 외 DDL 등)을 수행하는 동안에만 유효하기 때문에 짧은 라이프 사이클을 가지게 됨.

  - SqlSessionFactory : 애플리케이션 스코프 -> 싱글턴 패턴 사용

   - 애플리케이션 내에서 단 하나만 생성되도록 관리해야 함.

   - 애플리케이션이 실행되는 동안 SqlSessionFactory가 여러 번 빌드되지 않도록 하는 것이 가장 좋은 형태.

   - 애플리케이션이 실행되는 동안 여러 스래드(= SqlSession)에서 공유되기 때문에 긴 라이프 사이클을 가지게 됨.

  - SqlSession : 메서드 스코프 or 트랜잭션 -> 메서드 내부 또는 트랜잭션 범위 내부

   - 추후 웹 요소가 결합되면 HTTP 요청과 밀접한 생명 주기를 가져감. 즉, HTTP 요청이 발생했을 때 객체가 생성되고, 요청에 대한 HTTP 응답이 전송되면 객체를 폐기하면 되기 때문에 HTTP 요청과 유사한 스코프에 두는 것이 가장 올바른 방법.

   - 데이터베이스 작업이 끝난 후에는 반드시 close() 메서드를 호출하여 리소스를 반환해야 하며, 그렇지 않으면 연결 누수(Connection Leak)가 발생하게 되고 이는 메모리 누수(Memory Leak)로 이어질 수 있음.

 

3. MyBtis Configuration by XML

 - properties

  - 설정 파일에서 공통적인 속성을 정의하거나 외부 파일에서 값을 가져오는 태그.

  - 외부 프로퍼티 파일은 resource 하위의 경로를 기술하면 기술하면 됨.

 - settings

  - SqlSessionFactory 객체가 SqlSession 객체를 만들 때, 생성할 객체의 특성을 설정함.

  - settings 엘리먼트의 하위 엘리먼트들은 대부분 디폴트값을 가지며, 특별한 경우가 아니면 디폴트값을 사용해도 무방함.

 - typeAliases

  - MyBatis에서 사용할 자료형의 별칭을 선언함.

  - mapper의 쿼리를 작성할 때 ResultType이나 parameter 속성에 풀클래스명으로 사용해야 하는 클래스의 별칭을 등록하여 간략하게 사용할 수 있음.

 - environments

  - MyBatis에서 연동할 Database 정보를 등록함.

  - transactionManager : 트랜잭션 메니저를 JDBC 혹은 MANAGED로 설정할 수 있음.

   - JDBC : MyBatis API에서 제공하는 commit, rollback 메서드 등을 사용해서 트랜잭션을 관리하는 방식 (수동 commit)

   - MANAGED : MyBatis API 보다는 컨테이너가 직접 트랜잭션을 관리하는 방식 (자동 commit)

  - dataSource : 속성으로 커넥션풀 사용 여부를 POOLED와 UNPOOLED로 설정할 수 있음.

   - 설정 가능한 type 중 JNDI도 있는데, 이는 MyBatis에서 Connection 객체를 생성하여 관리하지 않고 Web Application의 설정을 따르겠다는 의미.

  - mappers

   - 사용하고자 하는 쿼리가 정의된 mapper 파일을 등록함.

   - <mapper resource=""> : (상대경로) 클래스패스에 위치한 xml 매퍼 파일 지정
   - <mapper url=""> : (절대경로) URL을 사용한 xml 매퍼 파일 지정
   - <mapper class=""> : 매퍼 인터페이스를 사용하는 인터페이스 위치 지정
   - <mapper name="">  : 패키지 지정으로 패키지 내 자동으로 매퍼 검색

Dynamic Query

1. MyBatis Dynamic Query

 - 일반적으로 검색 기능이나 다중 입력 처리 등을 수행할 경우, SQL을 실행하는 DAO를 여러 번 호출하거나 batch 기능을 이용하여 버퍼에 담아서 한번에 실행시키는 방식으로 쿼리를 구현함.

 - MyBatis에서는 이를 동적으로 제어할 수 있는 구문을 제공하여 쿼리를 좀 더 쉽게 구현할 수 있는 기능을 지원함.

 

2. if

 - 동적 쿼리를 구현할 때 가장 기본적으로 사용되는 구문.

 - 특정 조건을 만족하는 경우 내부의 구문을 쿼리에 포함함.

 

3. choose (when, otherwise)

 - Java의 if-else, switch 구문과 JSTL의 choose 구문과 유사하며 주어진 구문 중 한 가지만 수행할 때 사용함.

 

4. trim (where, set)

 - <trim> : 쿼리의 구문의 특정 부분을 없앨 때 쓰임.

 - <where> : 기존 쿼리의 WHERE 절을 동적으로 구현할 때 쓰임.

 - <set> : 기존 쿼리의 UPDATE SET 절을 동적으로 구현할 때 쓰임.

 

5. foreach

 - Java의 for문과 같은 역할을 하는 것으로, 동적 쿼리를 구현할 때 collection에 대한 반복 처리를 제공함.

 

6. bind

 - 특정 문장을 미리 생성하여 쿼리에 적용해야 할 경우 사용함.

 - '_parameter'를 통해 전달 받은 값에 접근하여 구문을 생성함.

MyBatis Dynamic Query (Java Provider) 구문

1. Java Provider

 - MyBatis에서 제공하는 기술로 어노테이션과 Java configuration을 이용하여 동적 쿼리를 작성할 수 있음.

 - 인터페이스에서 Provider Annotation을 통해 Provider 클래스를 type으로 설정하고 method로 쿼리ID를 설정하여 매핑함.

 

2. @Provider

 - 매퍼 인터페이스의 메서드에 어노테이션을 작성해야 함.

 - 어노테이션은 수행할 쿼리문에 따라 @SelectProvider, @InsertProvider, @UpdateProvider, @DeleteProvider로 작성함.

 - 어노테이션의 type 속성에는 Provider의 클래스 타입을, method에는 수행할 쿼리ID를 적음.

728x90
반응형

+ Recent posts