<< Back
blog 스프링프레임워크

스프링프레임워크 :: 2.스프링 기초 활용

이미지출처: pexels.com, Brett Sayles



Spring Bean 선언과 오토와이어링

Spring Bean 애너테이션

스프링 부트의 경우 @Component, @Service, @Controller, @Repository, @Bean, @Configuration 등으로 필요한 Bean들을 등록하고 필요한 곳에서 @Autowired 를 통해 주입받아 사용하는것이 일반적입니다.

다음 그림과 같이 @Service, @Controller, @Repository 는 모두 @Component 를 상속받고 있으며 해당 애너테이션으로 등록된 클래스들은 스프링 컨테이너에 의해 자동으로 생성되어 스프링 Bean 으로 등록 됩니다.

스프링프레임워크 시작하기에서 생성한 springStudy 프로젝트에 다음과 같이 Weapon 인터페이스와 GhotGun 클래스를 생성하도록 합니다.

package com.example.demo;

public interface Weapon {
	void fire();
}
package com.example.demo;
import org.springframework.stereotype.Component;

@Component
public class ShotGun implements Weapon {
	private String model = "Basic ShotGun";
	
	public String getModel() {
		return model;
	}

	public void setModel(String model) {
		this.model = model;
	}
	
	@Override
	public void fire() {
		System.out.println(model+" fire!!");
	}
}

오토와이어링

오토와이어링은 자동으로 스프링빈 객체를 특정 참조변수에 매핑해주는 것을 말하며 @Autowired 라는 애너테이션을 사용합니다. 필요에 따라 @Qualifier를 사용해 특정 빈 객체를 지정할수도 있습니다.

오토와이어링 예제 실행을 위해 Spring Boot 메인 애플리케이션인 SpringStudyApplication.java 를 다음과 같이 CommandLineRunner 인터페이스를 구현하는 것으로 수정 합니다. CommandLineRunner는 Spring Boot 애플리케이션(Context)이 시작되고 Command Line 인자를 받아 실행되는 코드를 구현하기 위해 사용 합니다. run() 메서드를 오버라이딩해야 합니다.

package com.example.demo;

@SpringBootApplication
public class SpringStudyApplication implements CommandLineRunner{
	@Autowired
	Weapon w;
	 
	public static void main(String[] args) {
		SpringApplication.run(SpringStudyApplication.class, args);		
	}

	public void run(String... args) throws Exception {
		w.fire();
	}
}

이렇게 구현함으로써 객체의 생성과 객체를 사용하는 클래스와의 종속관계가 없어지고 객체 참조를 애너테이션으로 간단하게 설정할 수 있습니다.


자바 설정 클래스를 이용한 Spring Bean 생성

Spring Bean 을 생성하는 또 다른 방법으로는 자바 설정 클래스를 이용하는 것입니다. 초기 스프링 기반 개발에서 Bean 생성은 xml 로 된 스프링 설정파일을 통해 이루어졌으며 지금은 자바 클래스에서 관련 설정을 대신하는 방법을 주로 사용합니다. 물론 필요에 따라 xml 설정은 아직도 사용이 가능합니다.

설정 클래스는 @Configuration 애너테이션을 클래스 선언부 앞에 추가 하면 됩니다. 또한 특정 타입을 리턴하는 메서드를 만들고 @Bean 애너테이션을 붙여주면 자동으로 해당 타입의 Bean 객체가 생성됩니다.

@Bean 애너테이션의 주요 내용은 다음과 같습니다.

package com.example.demo;

@Configuration
public class BasicConfiguration {
	@Bean
	public Weapon superShotGun() {
		ShotGun sg = new ShotGun();
		sg.setModel("Super ShotGun");
		return sg;
	}
}

위 코드를 추가한 다음 예제를 실행하게 되면 에러가 발생 합니다. 이유는 앞에서 @Component 를 이용해 Weapon 타입 객체를 생성 하고 설정 클래스에서 또다른 Weapon타입의 객체를 생성했기 때문입니다. 따라서 오토와이어링시 어떤 객체를 가져올지 결정하지 못하는 문제가 발생하는 것입니다.

문제 해결방법은 다음 두가지 방법 입니다.

Bean에 이름을 지정하는 방법은 다음과 같습니다.

이름을 명시하지 않는경우

이름을 명시하는 경우

오토와이어링시 이름 사용 @Autowired 에서 특정 이름의 Bean 을 가지고 오려면 @Qualifier 애너테이션을 사용 합니다.

여기서는 @Bean에 이름을 부여하고 찾는 것으로 다음과 같이 소스코드를 수정 합니다.

// BasicConfiguration.java
@Bean(name="super")
...

// SpringStudyApplication.java
@Autowired
@Qualifier("super")
Weapon w;
...

스프링 빈 라이프 사이클과 우선순위

스프링 빈은 기본적으로 객체생성 -> 의존설정 -> 초기화 -> 소멸 의 라이프사이클을 가집니다. 만일 빈의 라이프사이클 과정에 동작 시켜야 하는 코드가 있다면 @PostConstruct 와 @PreDestroy 애너테이션이 적용된 메서드에서 처리할 수 있습니다.

@PostConstruct

스프링 빈이 생성된 다음 호출되는 메서드를 지정할때 사용 합니다. 애너테이션 사용 대신 Bean 클래스를 InitializingBean 인터페이스를 구현 하도록 해서 afterPropertiesSet() 메서드를 오버라이딩 하는 방법도 있습니다. 이 경우는 객체생성후 의존설정이 끝난 다음에 호출됩니다.

@PostConstruct 를 사용한 메서드는 인자를 사용할 수 없으며 리턴은 허용되나 사용되지는 않습니다.

// ShotGun.java
@PostConstruct
public void init() {
  ..
}

또다른 방법으로는 @Bean 애너테이션에서 initMethod를 지정하는 방법 입니다. 이경우 의존설정이 끝나고 객체의 속성들을 초기화하는 단계에서 실행되는 메서드가 됩니다.

// BasicConfiguration.java
@Bean(initMethod = "init")
public ShotGun shotGun() {
  ...
}

// ShotGun.java
public void init() {
  ..
}

만일 앞에서 설명한 라이프사이클 관련 구현을 모두 사용하는 경우 실행 순서는 다음과 같습니다.

@PreDestroy

스프링 빈이 종료되기 직전 호출되는 메서드를 지정할때 사용 합니다. 애너테이션 사용 대신 Bean 클래스를 DisposableBean 인터페이스를 구현하도록 해서 destroy() 메서드를 오버라이딩 하는 방법도 있습니다.

// ShotGun.java
@PreDestroy
public void destroy() {
  ..
}

앞에서와 마찬가지로 @Bean 애너테이션에서 destroyMethod를 지정하는 방법도 있습니다.

// BasicConfiguration.java
@Bean(initMethod = "init", destroyMethod = "destroy")
public ShotGun shotGun() {
  ...
}

// ShotGun.java
public void destroy() {
  ..
}

@Scope

스프링 빈 객체의 인스턴스를 생성하고 유지하는 방법을 지정하는 애너테이션 입니다. @Component, @Bean 과 같이 사용할 수 있습니다.


스프링 AOP 예제

스프링프레임워크 시작하기에서 배운 AOP 를 앞에서 만든 기본예제를 가지고 적용해 보겠습니다. 가장 대표적으로 사용할 수 있는 횡단 관심사인 로깅을 특정 클래스의 모든 메서드 호출 전후에 적용하는 것을 목표로 합니다.

pom.xml에 spring-boot-starter-aop 추가

스프링 부트 프로젝트 생성시 spring-boot-starter-aop 가 추가되어 있어야 하며 혹시 추가되어 있지 않다면 먼저 pom.xml 에 다음과 같이 추가 합니다.

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

스프링 부트 어플리케이션에 AOP 설정

먼저 스프링 부트 어플리케이션에 AOP 동작을 위해 다음과 같이 @EnableAspectJAutoProxy 애너테이션을 추가 합니다.

@SpringBootApplication
@EnableAspectJAutoProxy
public class SpringStudyApplication implements CommandLineRunner{
  ...
}

Aspect 설정

애스팩트는 어드바이스와 포인트컷을 포함한 부가모듈로 스프링에서는 @Aspect 애너테이션을 사용한 클래스에서 설정하게 됩니다. 다음과 같이 로깅 처리를 위한 애스팩트 클래스를 만듭니다.

package com.example.demo;

@Aspect
@Component
public class LogAspect {
	Logger logger = LoggerFactory.getLogger(LogAspect.class);

	@Around("execution(* com.example.demo.ShotGun.*(..))")
	public Object logging(ProceedingJoinPoint pjp) throws Throwable {
		logger.info("Log start - " + pjp.getSignature().getDeclaringTypeName() + " / " + pjp.getSignature().getName());
		Object result = pjp.proceed();
		logger.info("Log finished - " + pjp.getSignature().getDeclaringTypeName() + " / " + pjp.getSignature().getName());
		return result;
	}
}

실행하면 fire() 메서드 전후 로그 메서드가 출력되 것을 확인할 수 있습니다. 지금까지 작성한 전체 실행 결과는 다음과 같습니다.

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.1.3.RELEASE)

2019-03-17 21:11:40.116  INFO 20736 --- [           main] com.example.demo.SpringStudyApplication  : Starting SpringStudyApplication on DinOfficeHP with PID 20736 (E:\Project\WSSpring\springStudy\target\classes started by dinfree in E:\Project\WSSpring\springStudy)
2019-03-17 21:11:40.118  INFO 20736 --- [           main] com.example.demo.SpringStudyApplication  : No active profile set, falling back to default profiles: default
init com.example.demo.ShotGun@584f5497
init com.example.demo.ShotGun@452c8a40
2019-03-17 21:11:40.438  INFO 20736 --- [           main] com.example.demo.SpringStudyApplication  : Started SpringStudyApplication in 0.463 seconds (JVM running for 1.042)
2019-03-17 21:11:40.440  INFO 20736 --- [           main] com.example.demo.LogAspect               : Log start - com.example.demo.ShotGun / fire
Super ShotGun fire!!
2019-03-17 21:11:40.443  INFO 20736 --- [           main] com.example.demo.LogAspect               : Log finished - com.example.demo.ShotGun / fire
destroy com.example.demo.ShotGun@452c8a40
destroy com.example.demo.ShotGun@584f5497
<< Back