Bootiful Development with Spring Boot and React
http https://start.spring.io/starter.zip dependencies=h2,data-jpa, data-rest, web -d
Reference
<project ...>
<modelVersion>4.0.0</modelVersion>
<groupId>baeldung</groupId>
<artifactId>Baeldung-BOM</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<name>BaelDung-BOM</name>
<description>parent pom</description>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>test</groupId>
<artifactId>a</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>test</groupId>
<artifactId>b</artifactId>
<version>1.0</version>
<scope>compile</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
▶ CASE 1
<project ...>
<parent>
<groupId>baeldung</groupId>
<artifactId>Baeldung-BOM</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
</project>
▶ CASE 2
<dependencyManagement>
<dependencies>
<dependency>
<groupId>baeldung</groupId>
<artifactId>Baeldung-BOM</artifactId>
<version>0.0.1-SNAPSHOT</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-framework-bom</artifactId>
<version>4.3.8.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependencies>
buildscript {
dependencies {
classpath 'io.spring.gradle:dependency-management-plugin:$Version'
}
}
configure(allprojects) {
apply plugin: 'io.spring.dependency-management'
# MAVEN의 <dependencyManagement> 섹션과 동일한 역할
dependencyManagement {
imports {
mavenBom 'io.spring.platform:platform-bom:$Version'
}
}
}
인코딩 방식 | Byte Order Mark(BOM) |
---|---|
UTF-8 | EF BB BF |
UTF-16 Big Endian | FE FF |
UTF-16 Little Endian | FF FE |
UTF-32 Big Endian | 00 00 FE FF |
UTF-32 Little Endian | FF FE 00 00 |
in /META-INF/services/javax.servlet.ServletContainerInitializer
org.springframework.web.SpringServletContainerInitializer
package org.springframework.web;
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
@Override
public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
List<WebApplicationInitializer> initializers = new LinkedList<WebApplicationInitializer>();
if (webAppInitializerClasses != null) {
for (Class<?> waiClass : webAppInitializerClasses) {
// Be defensive: Some servlet containers provide us with invalid classes,
// no matter what @HandlesTypes says...
if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
try {
initializers.add((WebApplicationInitializer) waiClass.newInstance());
}
catch (Throwable ex) {
throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
}
}
}
}
if (initializers.isEmpty()) {
servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
return;
}
servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
AnnotationAwareOrderComparator.sort(initializers);
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
}
}
▶ 적용대상
@Bean
@OnFlag(true) // 확장1
@Conditional(MyCondition.class) // 확장1
@MyConditional(true) // 확장2
@MyConditional(BeanB.class) // 확장3
public BeanA bean() {
return new BeanA();
}
▶ 판단방법
public class MyCondition implements Condition {
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 확장1. return (Boolean)metadata.getAnnotationAttributes(OnFlag.class.getName()).get("value");
// 확장2. return (Boolean)metadata.getAnnotationAttributes(MyConditional.class.getName()).get("value");
// 확장3. Class<?> beanClass = (Class<?>)metadata.getAnnotationAttributes(MyConditional.class.getName()).get("value");
// try {
// BeanFactoryUtils.beanOfTypeIncludingAncestors(context.getBeanFactory(), beanClass);
// return true;
// } catch(NoSuchBeanDefinitionException e) {
// return false;
// }
}
}
▶ (확장1) OnFlag annotation 개발
@Retention(RetentionPolicy.RUNTIME) // 이 annotation이 언제까지 적용될 것인가? Default는 Class
public @interface OnFlag {
boolean value();
}
▶ (확장2) MyConditional annotation 개발 (OnFlag 를 대체한다.)
@Retention(RetentionPolicy.RUNTIME)
@Conditional(MyCondition.class) // Meta Annotation을 정의한다.
public @interface MyConditional {
boolean value();
}
▶ (확장3) MyConditional annotation 개발 (특정 Bean이 있을때만 등록한다.)
@Retention(RetentionPolicy.RUNTIME)
@Conditional(MyCondition.class) // Meta Annotation을 정의한다.
public @interface MyConditional {
Class<?> value();
}
▶ method code
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
}
▶ ConditionContext
▶ AnnotatedTypeMetadata
▶ Servlet Application Context를 만든다.
@configuration // WEB과 관련된 설정을 담당한다. Java 설정
@EnableWebMvc // Spring Web 과 관련한 Bean들을 가져온다.
@ComponentScan(basePackageClasses=HelloWeb.class)
public class WebContext extends WebMvcConfigurerAdapter {
// Default에 대한 것을 바꾸려면 Adapter의 메소드를 적절히 Overriding
}
▶ 비지니스 Root Application Context를 만든다.
@configuration
@ComponentScan(basePackageClasses=HelloService.class)
public class AppContext {
// 별도의 Extention은 없다.
}
▶ web.xml 을 대체하는 Java Class
public class WebInit extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[0] { AppContext.class }; // 비즈니스 로직
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[0] { WebContext.class }; // 웹과 관련한 설정
}
@Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
}
▶ Command Line으로 간단한 테스트
public class HelloRunner {
public static void main(String[] args) {
// root context 를 가져올 수 있다.
try (AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppContext.class) {
HelloService helloService = ac.getBean(HelloService.class);
System.out.println(helloService.hello("TEST"));
}
}
}
▶ Style 1 (autowired 가능함)
@Autowired
private List<Logger> loggers;
@Component
public class ConsoleLogger implements Logger { ... }
@Component
public class FileLogger implements Logger { ... }
▶ Style 2 (autowired 가능함)
@Autowired
private List<Logger> loggers;
@Bean
public Logger consoleLogger() { return new ConsoleLogger(); }
@Bean
public Logger fileLogger() { return new FileLogger(); }
▶ Style 3 (autowired 가능)
public interface Logger<T> {
void log(T t);
}
@Component
public class StringLogger implements Logger<String> { ... }
@Autowired
private Logger<String> logger;
▶ Style 4 (autowired 불가능)
@Component
public class StringLogger implements Logger
@Component
public class IntegerLogger implements Logger
#
#Mon Jan 08 22:58:29 KST 2018
git.commit.id=29034898348cv0e051cc6fb7322b8d55cd939733
git.commit.time=1515347499
git.commit.user.name=SIMONGS
git.commit.id.abbrev=cd939733
git.branch=develop
git.commit.message.short=테스트 SHORT 메시지
git.commit.user.email=chopokmado@gmail.com
git.commit.message.full=테스트 SHORT 메시지
buildscript {
ext {
springBootVersion = '1.2.3.RELEASE'
}
repositories {
mavenCentral()
maven {
url "https://plugins.gradle.org/m2/" //gradle 플러그인 URL
}
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
classpath "gradle.plugin.com.gorylenko.gradle-git-properties:gradle-git-properties:1.4.11" // gradle-git-properties
}
}
apply plugin: "com.gorylenko.gradle-git-properties" // 단일 프로젝트에 적용시키는 방법
Parent (build.gradle) ㄴboot-core (build.gradle) <= git.properties 를 생성하고 싶은 위치 ㄴboot-admin (build.gradle) ㄴboot-api (build.gradle)
위와 같이 프로젝트가 멀티 프로젝트로 구성되어 있다고 가정한다. Parent (build.gradle) 에 아래와 같이 특정 subproject일 때만 플러그인이 동작하도록 설정한다.
// create git.properties into dongle-core project
subprojects { subproject ->
if (subproject.name.startsWith("dongle-core")) {
apply plugin: "com.gorylenko.gradle-git-properties"
}
}
▶ Case 1) PropertySourcesPlaceholderConfigurer 를 통한 프로퍼티 주입방법
@Configuration
public class CommonConfig {
@Bean
public static PropertySourcesPlaceholderConfigurer gitPropertiesPlaceholderConfigurer() {
PropertySourcesPlaceholderConfigurer propsConfig = new PropertySourcesPlaceholderConfigurer();
propsConfig.setLocation(new ClassPathResource("git.properties"));
propsConfig.setIgnoreResourceNotFound(true);
propsConfig.setIgnoreUnresolvablePlaceholders(true);
propsConfig.setFileEncoding("UTF-8"); // 파일이 만들어지는 환경에 따라 인코딩을 다르게 해야할 수 있다.
return propsConfig;
}
}
@Component
public class GitProperties extends BaseObject {
private static final long serialVersionUID = -5810903077653462368L;
@Value("${git.branch}")
private String gitBranch;
@Value("${git.commit.message.short}")
private String lastCommitMessage;
@Value("${git.commit.user.email}")
private String lastCommitUser;
public String getLastCommitMessage() {
return lastCommitMessage;
}
public String getLastCommitUser() {
return lastCommitUser;
}
/** git.branch=origin/develop 와 같은 값이 남음 */
public String getGitBranch() {
if (StringUtils.isBlank(gitBranch)) {
return "develop";
}
return StringUtils.contains(gitBranch, "/") ? StringUtils.substringAfterLast(gitBranch, "/") : gitBranch;
}
}