섹션6.자동 구성 기반 애플리케이션
메타 애노테이션과 합성 애노테이션
Annotaion : 사전적인 의미로 주석. 자바에서 사용될 떄는 코드 사이에 주석처럼 쓰여서 특별한 의미, 기능을 수행하도록 하는 기술. 즉, 프로그램에게 추가적인 정보를 제공해주는 메타데이터라고 볼 수 있다.
JDK 1.5부터도입된 기능으로 정보를 알려주는 기능과 컴파일러에게 문법 에러 체크를 제공하기도 한다.
DispatcherServlet에게 등록할 Bean을 알려주는 역할도 한다.
Meta-data : 데이터에 관한 구조화된 데이터로, 다른 데이터를 설명해주는 데이터.
메타 데이터는 데이터에 대한 데이터이다. 대량 정보 가운데 찾고있는 정보를 효율적으로 찾아내서 이용하기 위해 일적한 규칙에 따라 컨텐츠에 부여되는 데이터.
Meta-Annotaion : 다른 Annotaion 에서 사용되는 Annotaion을 말한다. @Controller와 @Service 어노테이션은 빈으로 등록되기 위해 @Component를 포함하고 있는데 이 어노테이션 안에서 사용되고 있는 어노테이션인 @Component를 메타 어노테이션이라고 부른다. 보통 커스텀 어노테이션을 만들 때 많이 사용된다.
그 외 @Target, @Retention, @Document 등등이 있다.
애노테이션을 다른 애노테이션의 메타 애노테이션으로 사용하기 위해 @Target 메타 애노테이션에 ElementType.ANNOTATION_TYPE 이 선언되어야 한다.
https://math-coding.tistory.com/182
[Java] Annotation, Meta data란?
이 글은 "자바 온라인 스터디"를 통해 공부한 내용을 작성하였습니다. Annotation이란? Anntation은 JDK 1.5부터 도입된 것으로 자바 소스 코드에 추가하여 사용할 수 있는 메타데이터의 일종입니다. 대
math-coding.tistory.com
https://sanghye.tistory.com/39
[Spring] Meta Annotation 이란?(@Target, @Retention)
Spring 에서는 Anntotation 사용에 대한 기능을 상당히 많이 제공하고 있습니다. 주로 사용하는 @Controller, @Service, @Repostiroy 등 많은 Annotation 이 존재합니다. 해당 Annotion 은 각 기능에 필요한 만큼 많은
sanghye.tistory.com
package tobyspring.helloboot;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@UnitTest
@interface FastUnitText {
}
// 어플리케이션 LifeCycle에서 annotaion이 유지되는 범위
// 컴파일 이후 런타임시에도 참조가능 주로 리플렉션이나 로깅에 사용
@Retention(RetentionPolicy.RUNTIME)
// Meta Annotaion으로 사용하기 위해 Annotation 타입에 적용 가능하도록 Target 정의
// 메소드 범위에서 사용
@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD})
// custom Test annotation 사용을 위해 meta annotaion @Test 사용
@Test
@interface UnitTest {
}
public class HelloServiceTest {
@FastUnitText
void simpleHelloService(){
SimpleHelloService helloService = new SimpleHelloService();
String ret = helloService.sayHello("Test");
Assertions.assertThat(ret).isEqualTo("Hello Test");
}
@UnitTest
void helloDecorator() {
HelloDecorator helloDecorator = new HelloDecorator(name -> name);
final String ret = helloDecorator.sayHello("Test");
Assertions.assertThat(ret).isEqualTo("*Test*");
}
}
Composed-Annotaion: 메타 애노테이션을 하나 이상을 적용해서 만드는 경우. @RestController의 경우 @Controller와 @ResponseBody를 사용하고 있다. 여러개의 애노테이션이 붙어있으면 복잡해 보이기도 하는데 커스텀 애노테이션을 만들어서 해당 애노테이션에서 사용하고 커스텀한 애노테이션을 붙이면 깔끔해 보인다.
package tobyspring.helloboot;
import org.springframework.boot.SpringApplication;
import tobyspring.config.MySpringBootApplication;
@MySpringBootApplication
public class HellobootSpringApplication {
public static void main(String[] args) {
//MySpringApplication.run(HellobootSpringApplication.class, args);
SpringApplication.run(HellobootSpringApplication.class, args);
} // end of main
} // end of class
package tobyspring.config;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Configuration
@ComponentScan
@EnableMyAutoConfiguration
public @interface MySpringBootApplication {
}
Bean Object의 역할과 구분
애플리케이션 빈 : 개발자가 사용할 빈 구성 정보 제공
- 애플리케이션 로직 빈 : 애플리케이션의 기능, 비지니스 로직, 도메인을 담고있는 빈
- HelloController, SimpleHelloService
- 애플리케이션 인프라스트럭쳐 빈 : 개발자가 만드는게 아닌 이미 만들어진 기능을 사용한다고 명시적으로 구성정보를 작성한 빈
- DataSource, JpaEntityManagerFactory, JdbcTransactionManager
컨테이너 인프라스트럭쳐 빈 : 스프링 컨테이너 자신이거나 스프링 컨테이너가 기능을 확장하며 빈으로 등록해서 사용하는 빈. 개발자가 요청한 것이 아닌 컨테이너 스스로 필요한 부분에서 등록하여 동작 시킴
- ApplicationContext, BeanFactory, BeanPostProcessor, DefaultAdvisorAutoProxyCreator, Enviroment, BeanFactoryPostProcessor
TomcatServletWebServerFactory와 DispatcherServlet은 애플리케이션 동작 시키는데 중요한 Bean들이고 없으면 서비스가 돌아가지 않기때문에 컨테이너 인프라스트럭쳐 빈이라고 생각할 수 있지만 스프링 컨테이너가 스스로 등록시키는 작업을 하지 않고 개발자가 명시적으로 선언을 해줘야 한다는 점에서 애플리케이션 인프라스트럭쳐 빈이라고 볼 수 있다.
HelloController와 SimpleHelloService는 @ComponentScan 애노테이션으로 스프링이 시작될 때 스캔을 하며 빈을 등록하는데 구성정보인 TomcatServletWebServerFactory와 DispatcherServlet는 어떻게 등록할까?
자동 구성정보(AutoConfiguration) 메카니즘을 사용한다.
자동구성정보는 어떤 구성방식으로 적용되는가?
애플리케이션 작동에 필요한 애플리케이션 인프라스트럭쳐 빈을 각각 @Configuration 애노테이션으로 설정 정보를 가진 Bean으로 만들어서 기능 구분을 하고 스프링부트가 애플리케이션 필요에 따라 해당하는 구성정보를 가진 빈을 등록하고 자동 적용한다.(AutoConfiguration)
로직 빈과 인프라스트럭쳐 빈 패키지 분리
AutoConfig 대상 객체 분리
package tobyspring.config.autoconfig;
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.DispatcherServlet;
import tobyspring.config.MyAutoConfiguration;
@MyAutoConfiguration
public class DispatcherServletConfig {
@Bean
public DispatcherServlet dispatcherServlet() {
return new DispatcherServlet();
}
}
package tobyspring.config.autoconfig;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import tobyspring.config.MyAutoConfiguration;
@MyAutoConfiguration
public class TomcatWebServerConfig {
@Bean
public ServletWebServerFactory servletWebServerFactory(){
return new TomcatServletWebServerFactory();
}
}
package tobyspring.config;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(MyAutoConfigImportSelector.class)
public @interface EnableMyAutoConfiguration {
}
AutoConfig 대상 동적으로 불러오는 구문을 가진 대상 Import하는 애노테이션
package tobyspring.config;
import java.util.ArrayList;
import java.util.List;
import org.springframework.boot.context.annotation.ImportCandidates;
import org.springframework.context.annotation.DeferredImportSelector;
import org.springframework.core.type.AnnotationMetadata;
public class MyAutoConfigImportSelector implements DeferredImportSelector {
private final ClassLoader classLoader;
public MyAutoConfigImportSelector(ClassLoader classLoader) {
this.classLoader = classLoader;
}
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
List<String> autoConfigs = new ArrayList<>();
// META-INF.spring.full-qualified-annotation-name.imports 라는 파일을 읽어와서
// auto config 대상들을 load한다
ImportCandidates.load(MyAutoConfiguration.class, classLoader).forEach(autoConfigs::add);
return autoConfigs.toArray(new String[0]);
}
}
AutoConfig 대상 객체를 동적으로 추가해서 가져오는 ImportSelector 객체 사용
package tobyspring.config;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Configuration;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Configuration(proxyBeanMethods = false)
public @interface MyAutoConfiguration {
}
@Configuration과 proxyBeanMethods
@Component 애노테이션이 클래스에 붙으면 스프링 컨테이너 런타임시 @ComponentScan에 의해 Bean이 등록되고 컨테이너에 의해 객체가 1개만 생성되도록 컨트롤 된다.
@Bean 애노테이션은 메소드 레벨 선언으로 개발자가 직접 제어가 불가능한 외부 라이브러리를 Bean으로 등록할 때 사용한다. 생성되는 객체는 모두 다르다
@Configuration 애노테이션이 붙은 클래스 내에서 @Bean메서드를 사용해 객체생성을 하면 프록시 패턴이 적용되어 등록되는 빈이 싱글턴으로 생성됨이 보장된다.
1개의 객체만 생성되야 하는 Bean이 @Bean으로 같은 객체가 여러개 생성되지 못하게 하려고 클래스에 @Configuration 애노테이션을 사용한다.
이를 제어하기 위해 proxyBeanMethods 설정값이 나왔는데 proxyBeanMethods = false 로 @Configuration을 설정할 시 @Bean으로 여러 객체를 생성할 수 있다.
이는 실수를 유발할 수 있는 상황이 생기기도 하지만
@Bean 애노테이션을 사용하여 객체 생성시 또 다른 bean을 의존하지 않아 객체를 여러개 호출하지 않는 코드로 만들지 않았을 경우 비싼 Proxy 객체를 생성하여 시간과 비용을 낭비하여 등록할 필요가 없기 때문
package tobyspring.study;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
public class ConfigurationTest {
@Test
void configuration(){
AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();
ac.register(MyConfig.class);
ac.refresh();
Bean1 bean1 = ac.getBean(Bean1.class);
Bean2 bean2 = ac.getBean(Bean2.class);
Assertions.assertThat(bean1.common).isSameAs(bean2.common);
}
@Test
void proxyCommonMethod() {
final MyConfigProxy myConfigProxy = new MyConfigProxy();
final Bean1 bean1 = myConfigProxy.bean1();
final Bean2 bean2 = myConfigProxy.bean2();
Assertions.assertThat(bean1.common).isSameAs(bean2.common);
}
static class MyConfigProxy extends MyConfig {
private Common common;
@Override
Common common() {
if (this.common == null) this.common = super.common();
return this.common;
}
}
@Configuration
static class MyConfig {
@Bean
Common common() {
return new Common();
}
@Bean
Bean1 bean1() {
return new Bean1(common());
}
@Bean
Bean2 bean2() {
return new Bean2(common());
}
}
static class Bean1 {
private final Common common;
Bean1(Common common) {
this.common = common;
}
}
static class Bean2 {
private final Common common;
Bean2(Common common) {
this.common = common;
}
}
private static class Common {
}
}