○ SpringBoot 레퍼런스 가이드 설정부분

# FLYWAY (FlywayProperties)
flyway.check-location=false # check that migration scripts location exists
flyway.locations=classpath:db/migration # locations of migrations scripts
flyway.schemas= # schemas to update
flyway.init-version= 1 # version to start migration
flyway.init-sqls= # SQL statements to execute to initialize a connection immediately after obtaining it
flyway.sql-migration-prefix=V
flyway.sql-migration-suffix=.sql
flyway.enabled=true
flyway.url= # JDBC url if you want Flyway to create its own DataSource
flyway.user= # JDBC username if you want Flyway to create its own DataSource
flyway.password= # JDBC password if you want Flyway to create its own DataSource

예제에 보면 1 이 등록된 것을 볼 수 있다. 하지만!!

Caused by: org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 1 errors
Field error in object 'flyway' on field 'initVersion': rejected value [1]; codes [typeMismatch.flyway.initVersion,typeMismatch.initVersion,typeMismatch.org.flywaydb.core.api.MigrationVersion,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [flyway.initVersion,initVersion]; arguments []; default message [initVersion]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'org.flywaydb.core.api.MigrationVersion' for property 'initVersion'; nested exception is java.lang.IllegalStateException: Cannot convert value of type [java.lang.String] to required type [org.flywaydb.core.api.MigrationVersion] for property 'initVersion': no matching editors or conversion strategy found]
    at org.springframework.boot.bind.PropertiesConfigurationFactory.validate(PropertiesConfigurationFactory.java:296)
    at org.springframework.boot.bind.PropertiesConfigurationFactory.doBindPropertiesToTarget(PropertiesConfigurationFactory.java:255)
    at org.springframework.boot.bind.PropertiesConfigurationFactory.bindPropertiesToTarget(PropertiesConfigurationFactory.java:227)
    at org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor.postProcessBeforeInitialization(ConfigurationPropertiesBindingPostProcessor.java:296)
    ... 26 more

ㅡ_-);; 그대로 따라해보면 저렇게 오류를 뱉는다.

스프링부트 1.2.3 에서 Flyway와 관련된 ... 부분에서 initVersion 항목이 Integer 가 아니라 MigrationVersion으로 변경이 되었다. 스프링부트 1.2.3 에서 제공하는 flywayDB는 3.1 버전으로, Flyway.class의 코드를 살펴보면

/**
 * Sets the version to tag an existing schema with when executing baseline.
 *
 * @param initVersion The version to tag an existing schema with when executing baseline. (default: 1)
 * @deprecated Use setBaselineVersion() instead. Will be removed in Flyway 4.0.
 */
@Deprecated
public void setInitVersion(MigrationVersion initVersion) {
    LOG.warn("Flyway.setInitVersion() is deprecated. Use setBaselineVersion() instead. Will be removed in Flyway 4.0.");
    this.baselineVersion = initVersion;
}
 
/**
 * Sets the version to tag an existing schema with when executing baseline.
 *
 * @param initVersion The version to tag an existing schema with when executing baseline. (default: 1)
 * @deprecated Use setBaselineVersion() instead. Will be removed in Flyway 4.0.
 */
@Deprecated
public void setInitVersion(String initVersion) {
    LOG.warn("Flyway.setInitVersion() is deprecated. Use setBaselineVersion() instead. Will be removed in Flyway 4.0.");
    this.baselineVersion = MigrationVersion.fromVersion(initVersion);
}

에서 보는 것처럼, 1, 2, 3으로 정의하던 initVersion 관련 항목이 @Depercated 처리되었다.

initVersion 으로 설정하는 부분

String initVersionProp = properties.getProperty("flyway.initVersion");
if (initVersionProp != null) {
    LOG.warn("flyway.initVersion is deprecated and will be removed in Flyway 4.0. Use flyway.baselineVersion instead.");
    setBaselineVersion(MigrationVersion.fromVersion(initVersionProp));
}

baselineVersion

String baselineVersionProp = properties.getProperty("flyway.baselineVersion");
if (baselineVersionProp != null) {
    setBaselineVersion(MigrationVersion.fromVersion(baselineVersionProp));
}

flyway의 릴리즈 노트를 보면 init 관련항목들이 baseline으로 변경된 것을 확인할 수 있었다. 두둥.

Issue 860 Deprecated init(), use baseline() instead.
Issue 860 Deprecated initVersion, use baselineVersion instead.
Issue 860 Deprecated initDescription, use baselineDescription instead.
Issue 860 Deprecated initOnMigrate, use baselineOnMigrate instead.
Issue 860 Deprecated FlywayCallback.beforeInit(), use FlywayCallback.beforeBaseline() instead.
Issue 860 Deprecated FlywayCallback.afterInit(), use FlywayCallback.afterBaseline() instead.
Issue 860 Deprecated MigrationState.PREINIT, use MigrationState.BELOW_BASELINE instead.
Issue 860 Deprecated MigrationType.INIT, use MigrationType.BASELINE instead.

◎ 정리

  • initVersion 보다는 baselineVersion으로 옮겨가자.
  • MigrationVersion 은 LATEST와 EMPTY 로 변환가능...
  • 스프링부트에서 사용한 외부 라이브러리에서 오류가 발생한다면, 외부라이브러리와 관련된 가이드를 살펴보자.
    • 스프링부트 레퍼런스 가이드에 반영이 안되어 있을수도 있다.


깔끔하게 보고 싶으면 아래 링크에서 보라.
https://gist.github.com/ihoneymon/56dd964336322eea04dc


19:04:00 ERROR c.i.i.s.s.system.MailServiceImpl - >> Occur Exception: Failed messages: com.sun.mail.smtp.SMTPSendFailedException: 530 5.7.0 Must issue a STARTTLS command first. a11sm6769399pdj.54 - gsmtp

위의 메시지가 나타난다면, compile "com.sun.mail:javax.mail" 의존성을 추가하자.

○ build.gradle

dependencies {
  //.. 중략
  compile "org.springframework:spring-context-support"
  /**
   * @see http://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#boot-features-email
   *
   * Javax 관련 의존성: com.sun.mail:javax.mail 과 javax.mail:javax.mail-api 두개만 존재한다.
   * @see https://github.com/spring-projects/spring-boot/blob/master/spring-boot-dependencies/pom.xml
   * 를 살펴보면 javax.mail:mail 관련한 의존성이 빠져있는 것을 볼 수 있다. ㅡ_-)> 흠...
   */
  compile "com.sun.mail:javax.mail"
  //.. 중략
}

spring-context-support, javax.mail 에 대한 의존성을 추가한다.

○ properties.yml

mail:
  host: smtp.gmail.com
  port: 587
  protocol: smtp
  default-encoding: UTF-8
  username: your-email@domain
  password: password
  smtp:
    start-tls-enable: true
    auth: true

○ MailConfig

메일발송에 사용할 템플릿엔진으로는 Thymeleaf(http://www.thymeleaf.org/doc/articles/springmail.html)를 참고하여 작성하였다. 기존에 사용하던 메일설정을 활용했다.

import java.util.Properties;

import javax.validation.constraints.NotNull;

import lombok.Data;
import lombok.ToString;

import org.hibernate.validator.constraints.NotBlank;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver;
import org.thymeleaf.templateresolver.TemplateResolver;

/**
 *
 * @author jiheon
 *
 * @see <a
 *      href="http://www.thymeleaf.org/doc/articles/springmail.html">http://www.thymeleaf.org/doc/articles/springmail.html</a>
 * @see <a
 *      href="http://stackoverflow.com/questions/25610281/spring-boot-sending-emails-using-thymeleaf-as-template-configuration-does-not">http://stackoverflow.com/questions/25610281/spring-boot-sending-emails-using-thymeleaf-as-template-configuration-does-not</a>
 */
@Data
@Configuration
@ConfigurationProperties(prefix = "mail", locations = { "classpath:properties/properties.yml" })
@ToString
public class MailConfig {
    private static final String MAIL_DEBUG = "mail.debug";
    private static final String MAIL_SMTP_STARTTLS_REQUIRED = "mail.smtp.starttls.required";
    private static final String MAIL_SMTP_AUTH = "mail.smtp.auth";
    private static final String MAIL_SMTP_STARTTLS_ENABLE = "mail.smtp.starttls.enable";

    @Data
    public static class Smtp {
        private boolean auth;
        private boolean startTlsRequired;
        private boolean startTlsEnable;
    }

    @NotBlank
    private String host;
    private String protocol;
    private int port;
    private String username;
    private String password;
    private String defaultEncoding;
    @NotNull
    private Smtp smtp;

    @Bean
    public JavaMailSender mailSender() {
        JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
        mailSender.setHost(getHost());
        mailSender.setProtocol(getProtocol());
        mailSender.setPort(getPort());
        mailSender.setUsername(getUsername());
        mailSender.setPassword(getPassword());
        mailSender.setDefaultEncoding(getDefaultEncoding());
        Properties properties = mailSender.getJavaMailProperties();
        properties.put(MAIL_SMTP_STARTTLS_REQUIRED, getSmtp().isStartTlsRequired());
        properties.put(MAIL_SMTP_STARTTLS_ENABLE, getSmtp().isStartTlsEnable());
        properties.put(MAIL_SMTP_AUTH, getSmtp().isAuth());
        properties.put(MAIL_DEBUG, true);
        mailSender.setJavaMailProperties(properties);
        return mailSender;
    }

    @Bean
    public TemplateResolver emailTemplateResolver() {
        TemplateResolver emailTemplateResolver = new ClassLoaderTemplateResolver();
        emailTemplateResolver.setPrefix("mails/");
        emailTemplateResolver.setSuffix(".html");
        emailTemplateResolver.setTemplateMode("HTML5");
        emailTemplateResolver.setCharacterEncoding("UTF-8");
        emailTemplateResolver.setCacheable(true);
        return emailTemplateResolver;
    }
}

○ MailServiceTest

@Slf4j
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
@ActiveProfiles("test")
@WebAppConfiguration
public class MailServiceTest {

    @Autowired
    private MailService mailService;
    @Test
    public void test() {
        ThymeleafMailMessage mailMessage = new ThymeleafMailMessage("test");
        mailMessage.setFrom("test-email@domain");
        mailMessage.setTo("test-email@domain");
        mailMessage.setSubject("[Test] mailTest");
        mailMessage.addAttribute("name", "Guest");
        mailMessage.addAttribute("imageResourceName", "imageResourceName");
        mailMessage.setEncoding("UTF-8");

        mailService.send(mailMessage);
    }

}

○ MailMessage 클래스들

● MailMessage

import java.io.File;
import java.util.Calendar;
import java.util.Collections;
import java.util.List;

import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;

import org.springframework.mail.SimpleMailMessage;

import com.google.common.collect.Lists;

@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class MailMessage extends SimpleMailMessage {
    private static final long serialVersionUID = 1830106734321133565L;

    private List<File> attachments;
    private String encoding;
    private boolean htmlContent;

    public MailMessage() {
        super();
        super.setSentDate(Calendar.getInstance().getTime());
        this.attachments = Lists.newArrayList();
    }

    public MailMessage addAttachment(File file) {
        if (null != file) {
            this.attachments.add(file);
        }
        return this;
    }

    public MailMessage removeAttachment(File file) {
        if (null != file && this.attachments.contains(file)) {
            this.attachments.remove(file);
        }
        return this;
    }

    public List<File> getAttachments() {
        return Collections.unmodifiableList(this.attachments);
    }

    public boolean isMultipart() {
        return !this.attachments.isEmpty();
    }
}

● ThymeleafMailMessage

import java.util.Map;

import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;

import com.google.common.collect.Maps;

/**
 * Thymeleaf MailMessage
 *
 * @author jiheon
 *
 */
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class ThymeleafMailMessage extends MailMessage {
    private static final long serialVersionUID = -2313892947287620959L;
    @Getter
    private final String templateName;
    private final Map<String, Object> attributes;

    public ThymeleafMailMessage(String templateName) {
        this.templateName = templateName;
        this.attributes = Maps.newHashMap();
        super.setHtmlContent(true);
    }

    public ThymeleafMailMessage addAttribute(String key, Object value) {
        this.attributes.put(key, value);
        return this;
    }

    public ThymeleafMailMessage removeAttribute(String key) {
        if (this.attributes.containsKey(key)) {
            this.attributes.remove(key);
        }
        return this;
    }

    public Map<String, Object> getAttributes() {
        return java.util.Collections.unmodifiableMap(this.attributes);
    }
}

○ MailService

● interface MailService

import java.util.List;

import org.springframework.mail.MailException;

/**
 * MailService
 *
 * @author jiheon
 *
 */
public interface MailService {

    void send(MailMessage message) throws MailException;

    void send(List<MailMessage> messages) throws MailException;
}

● class MailServiceImpl

import java.util.List;
import java.util.Locale;

import javax.annotation.PostConstruct;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.internet.MimeMessage;

import lombok.extern.slf4j.Slf4j;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mail.MailException;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;

@Slf4j
@Service
public class MailServiceImpl implements MailService {
    @Autowired
    private JavaMailSender mailSender;
    @Autowired
    private SystemMailService systemMailService;
    @Autowired
    private TemplateEngine templateEngine;

    @PostConstruct
    public void setUp() {
        if (systemMailService.hasSystemMailConfig()) {
            mailSender = systemMailService.getMailSender();
        }
        log.debug("MailSender: {}", mailSender);
    }

    @Override
    public void send(MailMessage message) throws MailException {
        sendMail(message);
    }

    private void sendMail(MailMessage message) {
        try {
            log.debug(">> Send mailMessage: {}", message);
            if (ThymeleafMailMessage.class.isAssignableFrom(message.getClass())) {
                doThymeleafMailMessageSend((ThymeleafMailMessage) message);
            } else {
                doSend(message);
            }
        } catch (Exception e) {
            log.error(">> Occur Exception: {}", e.getMessage());
        }
    }

    private void doThymeleafMailMessageSend(ThymeleafMailMessage thymeleafMailMessage) throws MessagingException {
        Context context = new Context(Locale.getDefault());
        context.setVariables(thymeleafMailMessage.getAttributes());
        thymeleafMailMessage.setText(templateEngine.process(thymeleafMailMessage.getTemplateName(), context));

        doSend(thymeleafMailMessage);
    }

    private void doSend(MailMessage message) throws MessagingException {
        try {
            MimeMessage mimeMessage = new MimeMessage(Session.getInstance(System.getProperties()));
            MimeMessageHelper helper = getMimeMessageHelper(message, mimeMessage);
            mailSender.send(helper.getMimeMessage());
        } catch (MessagingException e) {
            throw e;
        }
    }

    @Override
    public void send(List<MailMessage> messages) throws MailException {
        for (MailMessage mailMessage : messages) {
            sendMail(mailMessage);
        }
    }

    private MimeMessageHelper getMimeMessageHelper(MailMessage message, MimeMessage mimeMessage)
            throws MessagingException {
        MimeMessageHelper mimeMessageHelper = makeMessageHelper(message, mimeMessage);
        mimeMessageHelper.setFrom(message.getFrom());
        mimeMessageHelper.setTo(message.getTo());
        if (null != message.getCc()) {
            mimeMessageHelper.setCc(message.getCc());
        }
        if (null != message.getBcc()) {
            mimeMessageHelper.setBcc(message.getBcc());
        }

        mimeMessageHelper.setSubject(message.getSubject());
        mimeMessageHelper.setText(message.getText(), message.isHtmlContent());
        mimeMessageHelper.setSentDate(message.getSentDate());
        return mimeMessageHelper;
    }

    private MimeMessageHelper makeMessageHelper(MailMessage message, MimeMessage mimeMessage) throws MessagingException {
        MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, message.isMultipart());
        if (StringUtils.hasText(message.getEncoding())) {
            helper = new MimeMessageHelper(mimeMessage, message.isMultipart(), message.getEncoding());
        }
        return helper;
    }

}

○ 정리

  • compile "com.sun.mail:javax.mail" 을 선언하지 않고 테스트를 돌려보면

    19:04:00 ERROR c.i.i.s.s.system.MailServiceImpl - >> Occur Exception: Failed messages: com.sun.mail.smtp.SMTPSendFailedException: 530 5.7.0 Must issue a STARTTLS command first. a11sm6769399pdj.54 - gsmtp

다음과 같은 메시지를 볼 수가 있다. ㅡ_-);; 이것 땜시 반나절을 삽질을 해버렸네... 두둥...



스프링부트만 믿고 개발에 정진해왔다. 그러다가 시연해야할 떄가 되어 젠킨스를 통해서 개발서버에 빌드-배포된 war 파일을 실행하여 테스트하려고 하니 Thymeleaf template engine에서 template file 을 찾지못하는 문제가 발생했다.

이 문제가 왜 생기는지를 고민하고 검색해봐도 별다른 내용을 찾을 수가 없었다.

스프링부트 튜토리얼을 살펴보다가, 컨트롤러 영역에서 조금 다른 차이점을 찾아냈다.

@Controller
public class TestController {

    @RequestMapping("/test")
    public String test() {
        return "test";
    }
}

문제가 발생하는 화면의 컨트롤러에서는

@Controller
public class TestController {

    @RequestMapping("/test")
    public String test() {
        return "/test";
    }
}

와 같은 형태로 정의가 되어 있는 것이다.

차이를 발견했는가???

저 문제를 찾지 못해서 헤매였다. 1시간여를…. 흙… Orz…

해결방법

return "/test";return "test"; 으로 정의하면 된다.
bootRepackage로 빌드된 애플리케이션 배포본은 경로에 민감하게 반응하는 것으로 보인다.

저 ‘/‘ 때문에 ‘/test’ 에 대한 접근이 아니라 ‘//test’로 처리하기 때문에 나타나는 문제가 아닐까 추측해본다.

스프링부트 기본배너

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::   v1.3.0.BUILD-SNAPSHOT

스프링애플리케이션에서 배너와 관련된 설정

Class 정의

public static void main(String[] args) {
    SpringApplication app = new SpringApplication(MySpringConfiguration.class);
    app.setShowBanner(false);
    app.run(args);
}

외부설정 정의

spring.main.show_banner=false

배너 변경

SpringBoot가 실행될때 배너를 보여주게 되는데, 이를 변경하고 싶을 때는

  • banner.txt 생성
    • banner.txt 파일을 root classpath(src/main/resource)에 위치
    • 별도의 위치에 두고 외부설정(properties or yaml)으로 banner.location 을 선언하고 위치를 정의

배너변경 결과

11:26:18,781 |-INFO in ch.qos.logback.classic.gaffer.ConfigurationDelegate@1c4e4471 - About to instantiate appender of type [ch.qos.logback.core.ConsoleAppender]
11:26:18,783 |-INFO in ch.qos.logback.classic.gaffer.ConfigurationDelegate@1c4e4471 - Naming appender as [STDOUT]
11:26:18,921 |-WARN in ch.qos.logback.core.ConsoleAppender[STDOUT] - This appender no longer admits a layout as a sub-component, set an encoder instead.
11:26:18,921 |-WARN in ch.qos.logback.core.ConsoleAppender[STDOUT] - To ensure compatibility, wrapping your layout in LayoutWrappingEncoder.
11:26:18,921 |-WARN in ch.qos.logback.core.ConsoleAppender[STDOUT] - See also http://logback.qos.ch/codes.html#layoutInsteadOfEncoder for details
11:26:18,924 |-INFO in ch.qos.logback.classic.gaffer.ConfigurationDelegate@1c4e4471 - Setting level of logger [org.thymeleaf] to INFO
11:26:18,931 |-INFO in ch.qos.logback.classic.gaffer.ConfigurationDelegate@1c4e4471 - Setting level of logger [org.springframework.web] to DEBUG
11:26:18,931 |-INFO in ch.qos.logback.classic.gaffer.ConfigurationDelegate@1c4e4471 - Setting level of logger [org.springframework] to INFO
11:26:18,931 |-INFO in ch.qos.logback.classic.gaffer.ConfigurationDelegate@1c4e4471 - Setting level of logger [org.hibernate] to INFO
11:26:18,931 |-INFO in ch.qos.logback.classic.gaffer.ConfigurationDelegate@1c4e4471 - Setting level of logger [org.eclipse.jetty] to INFO
11:26:18,931 |-INFO in ch.qos.logback.classic.gaffer.ConfigurationDelegate@1c4e4471 - Setting level of logger [jndi] to INFO
11:26:18,934 |-INFO in ch.qos.logback.classic.gaffer.ConfigurationDelegate@1c4e4471 - Setting level of logger [ROOT] to DEBUG
11:26:18,937 |-INFO in ch.qos.logback.classic.gaffer.ConfigurationDelegate@1c4e4471 - Attaching appender named [STDOUT] to Logger[ROOT]

  _   _     U  ___ u  _   _   U _____ u __   __  __  __    U  ___ u  _   _     
 |'| |'|     \/"_ \/ | \ |"|  \| ___"|/ \ \ / /U|' \/ '|u   \/"_ \/ | \ |"|    
/| |_| |\    | | | |<|  \| |>  |  _|"    \ V / \| |\/| |/   | | | |<|  \| |>   
U|  _  |u.-,_| |_| |U| |\  |u  | |___   U_|"|_u | |  | |.-,_| |_| |U| |\  |u   
 |_| |_|  \_)-\___/  |_| \_|   |_____|    |_|   |_|  |_| \_)-\___/  |_| \_|    
 //   \\       \\    ||   \\,-.<<   >>.-,//|(_ <<,-,,-.       \\    ||   \\,-. 
(_") ("_)     (__)   (_")  (_/(__) (__)\_) (__) (./  \.)     (__)   (_")  (_/  


11:26:19 DEBUG o.s.w.c.s.StandardServletEnvironment - Adding [servletConfigInitParams] PropertySource with lowest search precedence
11:26:19 DEBUG o.s.w.c.s.StandardServletEnvironment - Adding [servletContextInitParams] PropertySource with lowest search precedence
11:26:19 DEBUG o.s.w.c.s.StandardServletEnvironment - Adding [jndiProperties] PropertySource with lowest search precedence
11:26:19 DEBUG o.s.w.c.s.StandardServletEnvironment - Adding [systemProperties] PropertySource with lowest search precedence
11:26:19 DEBUG o.s.w.c.s.StandardServletEnvironment - Adding [systemEnvironment] PropertySource with lowest search precedence

위와 같은 결과를 볼 수 있다.

참고사항

SpringBoot

이미지 to ASCII CODE image


SpringBoot 1.2.0.RELEASE 부터 추가된 @SpringBootApplication.

/**
 * Indicates a {@link Configuration configuration} class that declares one or more
 * {@link Bean @Bean} methods and also triggers {@link EnableAutoConfiguration
 * auto-configuration} and {@link ComponentScan component scanning}. This is a convenience
 * annotation that is equivalent to declaring {@code @Configuration},
 * {@code @EnableAutoConfiguration} and {@code @ComponentScan}.
 *
 * @author Phillip Webb
 * @since 1.2.0
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Configuration
@EnableAutoConfiguration
@ComponentScan
public @interface SpringBootApplication {

}

1.1.9.RELEASE 까지는 Application Endpoint에 클래스에

@Configuration
@EnableAutoConfiguration
@ComponentScan
public class Application{
//...
}

3개 애노테이션을 사용해야했던 것을 하나로 줄여주었다.


1···89101112

+ Recent posts