스프링프레임워크 :: 2.스프링 기초 활용
Spring Bean 선언과 오토와이어링
Spring Bean 애너테이션
스프링 부트의 경우 @Component, @Service, @Controller, @Repository, @Bean, @Configuration 등으로 필요한 Bean들을 등록하고 필요한 곳에서 @Autowired 를 통해 주입받아 사용하는것이 일반적입니다.
다음 그림과 같이 @Service, @Controller, @Repository 는 모두 @Component 를 상속받고 있으며 해당 애너테이션으로 등록된 클래스들은 스프링 컨테이너에 의해 자동으로 생성되어 스프링 Bean 으로 등록 됩니다.
- 애너테이션의 구분은 사용 목적에 따라 세분화된 것으로 구체적인 사용은 웹이나 JPA 구현에서 다룬다.
- @Bean 은 @Configuration 애너테이션으로 등록된 자바 설정 클래스에서 빈을 생성할 때 사용.
스프링프레임워크 시작하기
에서 생성한 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!!");
}
}
- Weapon 인터페이스는 기본 무기를 정의.
- ShotGun 클래스는 무기중 하나로 모델명을 필드로 하고 fire() 메서드를 통해 ShotGun 발사를 표현.
오토와이어링
오토와이어링은 자동으로 스프링빈 객체를 특정 참조변수에 매핑해주는 것을 말하며 @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();
}
}
- @Autowired 애너테이션을 사용해 ShotGun 인스턴스를 스프링컨테이너로 부터 받아옴.
- run() 메서드에서 w.fire() 메서드 호출.
이렇게 구현함으로써 객체의 생성과 객체를 사용하는 클래스와의 종속관계가 없어지고 객체 참조를 애너테이션으로 간단하게 설정할 수 있습니다.
자바 설정 클래스를 이용한 Spring Bean 생성
Spring Bean 을 생성하는 또 다른 방법으로는 자바 설정 클래스를 이용하는 것입니다. 초기 스프링 기반 개발에서 Bean 생성은 xml 로 된 스프링 설정파일을 통해 이루어졌으며 지금은 자바 클래스에서 관련 설정을 대신하는 방법을 주로 사용합니다. 물론 필요에 따라 xml 설정은 아직도 사용이 가능합니다.
설정 클래스는 @Configuration 애너테이션을 클래스 선언부 앞에 추가 하면 됩니다. 또한 특정 타입을 리턴하는 메서드를 만들고 @Bean 애너테이션을 붙여주면 자동으로 해당 타입의 Bean 객체가 생성됩니다.
@Bean 애너테이션의 주요 내용은 다음과 같습니다.
- @Configuration 설정된 클래스의 메서드에서 사용가능.
- 메서드의 리턴 객체가 스프링 빈 객체임을 선언함.
- 빈의 이름은 기본적으로 메서드 이름이 됨.
- @Bean(name=”name”) 으로 이름 변경 가능.
- @Scope 을 통해 객체 생성을 조정할 수 있음.
- @Component 애너테이션을 통해 @Configuration 없이도 빈 객체를 생성할수도 있음.
- 빈 객체에 init(), destroy() 등 라이프사이클 메서드를 추가한 다음 @Bean 에서 지정할 수 있음.
package com.example.demo;
@Configuration
public class BasicConfiguration {
@Bean
public Weapon superShotGun() {
ShotGun sg = new ShotGun();
sg.setModel("Super ShotGun");
return sg;
}
}
Super ShotGun
이라는 모델명을 가지는 새로운ShotGun
객체를 스프링 Bean 으로 생성.
위 코드를 추가한 다음 예제를 실행하게 되면 에러가 발생 합니다. 이유는 앞에서 @Component 를 이용해 Weapon
타입 객체를 생성 하고 설정 클래스에서 또다른 Weapon
타입의 객체를 생성했기 때문입니다. 따라서 오토와이어링시 어떤 객체를 가져올지 결정하지 못하는 문제가 발생하는 것입니다.
문제 해결방법은 다음 두가지 방법 입니다.
- @Primary: @Bean 혹은 @Component 에 함께 사용하며 객체 생성의 우선권을 부여.
- @Qualifier: @Autowired에 함께 사용하며 Bean 의 이름이 같은 객체를 찾음.
Bean에 이름을 지정하는 방법은 다음과 같습니다.
이름을 명시하지 않는경우
- @Component: 소문자로 시작하는 클래스이름이 자동으로 사용됨.(예제에서는 shotGun)
- @Bean: 소문자로 시작하는 메서드이름이 자동으로 사용됨.(예제에서는 superShotGun)
이름을 명시하는 경우
- @Component: @Component(“이름”)과 같이 사용.
- @Bean: @Bean(name=”이름”)과 같이 사용.
오토와이어링시 이름 사용
@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() {
..
}
만일 앞에서 설명한 라이프사이클 관련 구현을 모두 사용하는 경우 실행 순서는 다음과 같습니다.
- @PostConstruct
- afterPropertiesSet()
- 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 과 같이 사용할 수 있습니다.
- 기본은 singleton
- Singleton : 싱글턴 객체 유지
- Prototype : 어플리케이션 요청시(getBean()) 마다 새로운 인스턴스 생성
- Request : HTTP 요청별로 인스턴스화 되어 요청이 끝나면 소멸 -> web
- Session : HTTP 세션별로 인스턴스화 되며 세션이 끝나면 소멸 -> web
- Global session : 포틀릿 기반
- Thread : 새로운 스레드에 대해 새로운 인스턴스 생성, 동일 스레드에서는 같은 인스턴스 사용
- Custom : 커스텀구현 스코프를 사용하는 경우
스프링 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;
}
}
- Logger 는 slf4j 를 사용.
- @Around: 애스팩트를 적용할 곳을 지정하는 애너테이션으로 포인트컷에 해당.
- 여기서는 ShotGun 클래스의 모든 메서드를 대상으로 지정.
- 메서드 인자인 ProceedingJoinPoint 를 이용해 메서드 시작전과 종료시 메시지를 출력.
실행하면 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