tomcat의 shutdown.sh 를 실행해서 서비스는 불가능 한 상태가 되지만 tomcat 프로세스는 살아 있는 경우가 있다.

tomcat의 서블릿이 

 

@WebListener

public class SchedulerManager implements ServletContextListener {

    @Override

    public void contextDestroyed(ServletContextEvent sce) {

        for (ThreadPoolTaskScheduler sch : SchedulingFongifurer.threadPoolTaskSchedulerList) { // 스케줄러 설정 class에서 스제출러 설정 목록을 static 으로 저장해 둔 목록

             sch.shutdown();

        }

    }

}

1. 설정

그래이들

implementation 'org.springframework.boot:spring-boot-starter-cache'

config 설정

@Configuration
@EnableCaching
public class CacheConfig {
}

 

2. 사용

캐시 할 내용에 @Cacheable

캐시 삭제시 @CacheEvict

캐시 업데이트 시 @Cacheput

 

import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

import java.util.Collection;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;

@Service
public class CacheService {

    private final CacheManager cacheManager;

    public CacheService(CacheManager cacheManager) {
        this.cacheManager = cacheManager;
    }

    @Cacheable(value = "random", key = "#id")
    public String cacheRandom( String id) {
        Random random = new Random();
        return id + "_" + random.nextInt(100);
    }

    @CacheEvict(value = "random", key = "#id")
    public void cacheRandomEvict(String id) {

    }

    public void cacheAll() {
        Collection<String> cacheNames = cacheManager.getCacheNames();
        for (String cacheName : cacheNames) {
            System.out.println("cacheName = " + cacheName);
        }
        Cache random = cacheManager.getCache("random");
        System.out.println("random = " + random);

        for (String cacheName : cacheNames) {
            Cache cache = cacheManager.getCache(cacheName);

            if (cache != null) {
                ConcurrentHashMap nativeCache = (ConcurrentHashMap) cache.getNativeCache();
                System.out.println("nativeCache = " + nativeCache);
                System.out.println("nativeCache type = " + nativeCache.getClass().getName() );
                ConcurrentHashMap cacheMap = (ConcurrentHashMap) nativeCache;
                cacheMap.forEach((strKey, strValue)->{
                    System.out.println( strKey +" : "+ strValue );
                });
            }
        }

    }
}

CacheManger (구현체 ConcurrentMapCacheManager )를 통해서 캐시 이름 목록을 가져오고

다시 이름으로 Cache 를 가져오고

가져온 캐시를 ConcurrentHashMap으로 변환 후 순회한다.

SpringBoot + Thymeleaf 로 작업 시 

 

html 변경이 바로 적용되지 않는 문제가 있다.

html 부분이나 스크립트 부분이나 고치면서 계속 확인 해야 하는 작업이 필요한 경우 

재실행하는거는 여간 귀찮은 일이 아니다.

 

applicatoin.properties에 아래와 같이 설정을 하면 바로 변경된 html을 확인 할 수 있다.

 

spring.thymeleaf.cache=false
spring.thymeleaf.prefix=file:src/main/resources/templates/

 

인텔리제이 Community 버전도 잘 된다.

 

 

 

Thymeleaf 적용한 프로젝트에서 title 부분을 전체 똑같이 적용해 달라는 요청이 있었다.

 

Thymeleaf Layout 이 적용된 상태에서

 

layout.html 

의 <title> 부분이 없는 상태였고

각 페이지에서 <title> 값을 설정한 상태였다.

layout.html 에 <title>LAYOUT</title> 을 추가해도

각 페이지의 <title> 이 layout.html 의 title을 대체하게 된다.

 

layout:title-pattern 속성을 이용하면 전체 페이지의 title을 패턴화 할수 있다.

lauout.html 에 아래 태그를 추가하면

<title layout:title-pattern="$LAYOUT_TITLE - $CONTENT_TITLE">MY SITE</title>

$LAYOUT_TITLE - 레이아웃의 타이틀 (구버전 : $DECORATOR_TITLE 는 deprecated 됨)

$CONTENT_TITLE - 각 페이지의 타이틀

 

실제 각 페이지에서는

<title>MY SITE - 각페이지 타이틀</title> 과 같이 된다.

 

해당 요청 사항은

<title layout:title-pattern="$LAYOUT_TITLE">MY SITE</title>

로 해결했다.

 

참고 : title-pattern - Thymeleaf Layout Dialect (ultraq.github.io)

 

스프링부트 로컬에서 여러개 띄울때 세션 끊어지는 것 방지

 

application.properties에

server.session.cookie.name=특정단어

를 추가하면 된다.

스프링에서 파라미터 유효성 검사를 위한 커스터 마이징

날짜형식의 String을 유효성 체크 하기 위한 커스터 마이징

날짜형식 : 2021-09-10

 

1. 의존성 추가

 

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

 

2. 사용자 정의 어노테이션을 위한 인터페이스 생성

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import javax.validation.Constraint;
import javax.validation.Payload;

@Documented
@Constraint(validatedBy = DateValidator.class)
@Target( {ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DateValid {
    String message() default "날짜형식이 옳바르지 않습니다.";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

 

3. 유효성 검사를 실행할 클래스 생성

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

public class DateValidator implements ConstraintValidator<DateValid, String>{

    @Override
    public void initialize(DateValid constraintAnnotation) {

    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if (value == null ||  value.length() == 0) return true;
        try {
            LocalDate.from(LocalDate.parse(value, DateTimeFormatter.ofPattern("yyyy-MM-dd")));
        } catch (DateTimeParseException e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }
}

 

4. DTO에 사용자 정의 유효성 검사 어노테이션 추가

 

@DateValid(message = "날짜형식이 옳바르지 않습니다.")
private String date;

 

 

스프링부트에서 @Valid로 유효성 검사 시

Integer 등 숫자형에 문자열이 입력된 경우 typeMismatch 에러가 발생하며 아래와 같은 메세지가 결과물로 온다.

Failed to convert property value of type java.lang.String to required type java.lang.Integer for property age; nested exception is java.lang.NumberFormatException: For input string: "abcd"

 

이 문구를 바로 사용자에게 보여줄 수 없으므로 해당 에러인 경우 에러 메세지를 커스텀화 해야 한다.

 

dto

@Getter
@Setter
@ToString
public class PersonForm {

	@NotBlank(message="이름을 입력해 주세요.")
	@Size(min=2, max=30, message="글자수가 안맞아요.")
	private String name;
	
	@NotNull(message="나이를 입력해 주세요.")
	@Min(value=18, message="나이는 최소한 18세이상으로 입력하세요.")
	private Integer age;
}

 

controller

	@PostMapping("/form")
	public String validation_valid(@Valid PersonForm personForm, BindingResult bindingResult) {
		if (bindingResult.hasErrors()) {
			return TEMPLATE_DIR.concat("form");
		}
		return "redirect:/" + TEMPLATE_DIR.concat("result");
	}

 

 

html

<form id="frm" action="#" method="post" th:action="@{/validation/form}" th:object="${personForm}">
	name : <input th:field="*{name}"><br>
	<div class="error" th:if="${#fields.hasErrors('name')}" th:errors="*{name}"> Name Error</div>
	age : <input th:field="*{age}"><br>
	<div th:if="${#fields.hasErrors('age')}" th:errors="*{age}"> Age Error</div>
	<button type="submit">Submit</button>
</form>

 

이런 구성인 경우 age에 문자를 입력 시 위와 같은 에러 메세지가 표시된다.

 

스프링부트인 경우

resources 폴더아래에

messages.properties 파일을 생성하고 아래와 같이 입력해 주면 된다.

typeMismatch.java.lang.Integer=숫자로 입력해 주세요.

thymeleaf 에서 외부 사이트 특정 부위 insert(include) 하기

 

html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>

<h1>head</h1>

<!-- 주소 :: 태그명.클래스명 -->
<div th:insert="https://www.thymeleaf.org :: section.description" >...</div>

<h1>foot</h1>


</body>
</html>

 

config 파일에 아래와 같이 설정해 줘야 한다.

configuration 파일이 없다면 임의로 하나 만들면 된다.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.thymeleaf.templateresolver.UrlTemplateResolver;

@Configuration
public class SpringTemplateConfig {
    // UrlTemplateResolver를 빈으로 등록
    @Bean
    public UrlTemplateResolver urlTemplateResolver() {
        return new UrlTemplateResolver();
    }
}

스프링부트 자동 빌드 - 자바, thymeleaf 자동 리로드, autoreload, livereload

 

 

pom.xml에 스프링부트 데트툴 디팬던시 추가

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-devtools</artifactId>
  <scope>runtime</scope>
  <optional>true</optional>
</dependency>

 

 

application.yml 에 라이브리로드 추가

spring:
  devtools:
    livereload:
      enabled: true
    restart:
      enabled: true

또는

spring.devtools.livereload.enabled=true

spring.devtools.restart.enabled=true

 

livereload는 뷰단 파일 thymeleaf 파일등 변경되었을때 다시 로드 

restart는 자바파일 변경 시 재 빌드.

 

이클립스 메뉴 Project > Build Automatically 체크

 

1. jdk 1.8 설치

openjdk를 다운로드 받아서 설치한다.

oralce java를 다운로드 받아서 설치해도 상관없지만 zip버전을 제공하지 않으므로 

openjdk를 다운로드 받아 설치한다.

 

https://github.com/ojdkbuild/ojdkbuild

중간쯤에 Downloads for Windows x86_64 항목이 있다. 여기서 zip파일을 다운로드 한다.

openjdk 다운로드

d:\에 springboot라는 폴더를 생성해서 이곳에 다운받은 openjdk를 압축해제 합니다.

폴더명이 너무 길어서 버전만 알수 있도록 정리했습니다.

이후 이클립스 설치 이후 이클립스 설정에서 해당 폴더로 연결을 할겁니다.

 

2. 이클립스 설치

이클립스를 다운받아 설치합니다. 아래 링크로 이동하여 파일을 다운받습니다.

https://www.eclipse.org/downloads/

최신버전보다는 어느정도  지난 버전을 받는걸 추천합니다.

다운로드 버튼 아래의 Download Packages 링크를 클릭하고 들어가서

다른 버전을 받을 수 있습니다.

 

자신의 운영체제에 맞는 이클립스를 선택해서 다운로드 하면 됩니다.

 

다운로드시 미러 사이트를 선택할 수 있는데 한국의 카카오를 선택하면 빠른 속도로 다운로드가 가능합니다.

 

다운받은 zip파일을 D:\springboot 로 복사하여 압축을 해제 합니다.

 

D:\springboot\eclipse 폴더로 이동하여 eclipse.ini 파일을 문서편집기로 열어서 -vmargs 위에 -vm을 추가합니다.

다운받은 openjdk를 이클립스에서 기본으로 사용하도록 하는 설정입니다.

-vm
D:\springboot\openjdk-1.8.0.222-2.b10\bin\javaw.exe

eclipse.ini 파일

-startup
plugins/org.eclipse.equinox.launcher_1.5.200.v20180922-1751.jar
--launcher.library
plugins/org.eclipse.equinox.launcher.win32.win32.x86_64_1.1.900.v20180922-1751
-product
org.eclipse.epp.package.jee.product
-showsplash
org.eclipse.epp.package.common
--launcher.defaultAction
openFile
--launcher.defaultAction
openFile
--launcher.appendVmargs
-vm
D:\springboot\openjdk-1.8.0.222-2.b10\bin\javaw.exe
-vmargs
-Dosgi.requiredJavaVersion=1.8
-Dosgi.instance.area.default=@user.home/eclipse-workspace
-XX:+UseG1GC
-XX:+UseStringDeduplication
--add-modules=ALL-SYSTEM
-Dosgi.requiredJavaVersion=1.8
-Dosgi.dataAreaRequiresExplicitInit=true
-Xms1024m
-Xmx1024m
--add-modules=ALL-SYSTEM

 

이클립스를 실행하면 workspace를 선택하는 창이 나옵니다. springboot에 workspace라는 폴더를 만들고 해당 폴더를 선택합니다.

 

 

 

3. 이클립스에 스프링(STS) 설치

이클립스가 실행되면 메뉴 Help > Eclipse Marketplace 메뉴를 클릭하여 

marketplace로 들어갑니다.

검색창에 sts를 입력하고 검색합니다.

Spring Tools 4 를 선택하여 Install 합니다.

 

 

4. 프로젝트 생성 및 테스트 페이지 접속

메뉴 : File > New > Other > Spring Boot > Spring Starter Project 를 선택하고 Next 클릭

 

 

 

 

 

<?xml version="1.0" encoding="UTF-8"?>

앞에 에러표시가 나는 경우 프로젝트 진행에는 문제가 없습니다. 하지만 x박이 계속뜨므로

properties에

<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version> 

를 추가합니다.

	<properties>
		<java.version>1.8</java.version>
		<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version> <!-- prevent pom config error-->
	</properties>

 

 

com.example.demo 패키지안에 DemoApiController class를 새로 만듭니다.

package com.example.demo;

import java.util.HashMap;
import java.util.Map;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class DemoApiController {

	@GetMapping("/demoapistring")
	public String demoapistring() {

		return "데모 스트링 타입 리턴";
	}
	
	@GetMapping("/demoapi")
	public Map<String, Object> demoapi() {
		Map<String, Object> map = new HashMap<>();
		map.put("name", "홍길동");
		map.put("birthday", 15920505);
		return map;
	}
}

 

DemoApplication.java 파일을 열고 Run As > Spring Boot App 을 클릭하여 실행합니다.

Demo(프로젝트명)Application.java 는 스프링 부트의 시작점 입니다.

 

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

2019-08-21 15:53:06.169  INFO 13044 --- [           main] com.example.demo.DemoApplication         : Starting DemoApplication on WIN-F2I020EETH2 with PID 13044 (D:\springboot\workspace\demo\target\classes started by kyoborealco in D:\springboot\workspace\demo)
2019-08-21 15:53:06.171  INFO 13044 --- [           main] com.example.demo.DemoApplication         : No active profile set, falling back to default profiles: default
2019-08-21 15:53:06.807  INFO 13044 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2019-08-21 15:53:06.827  INFO 13044 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2019-08-21 15:53:06.827  INFO 13044 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.22]
2019-08-21 15:53:06.897  INFO 13044 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2019-08-21 15:53:06.897  INFO 13044 --- [           main] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 697 ms
2019-08-21 15:53:07.042  INFO 13044 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2019-08-21 15:53:07.168  INFO 13044 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2019-08-21 15:53:07.171  INFO 13044 --- [           main] com.example.demo.DemoApplication         : Started DemoApplication in 1.216 seconds (JVM running for 1.737)
2019-08-21 15:53:15.125  INFO 13044 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2019-08-21 15:53:15.125  INFO 13044 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2019-08-21 15:53:15.129  INFO 13044 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 4 ms
2019-08-21 15:56:16.330  INFO 13044 --- [n(12)-127.0.0.1] inMXBeanRegistrar$SpringApplicationAdmin : Application shutdown requested.
2019-08-21 15:56:16.332  INFO 13044 --- [n(12)-127.0.0.1] o.s.s.concurrent.ThreadPoolTaskExecutor  : Shutting down ExecutorService 'applicationTaskExecutor'

콘솔 로그를 보면 톰캣이 8080으로 기동되었다고 표시됩니다.

http://localhost:8080/demoapistring

 

http://localhost:8080/demoapi

@RestController 을 추가한 경우

return 타입이 Object 인 경우 해당 object 에 맞게 json 형식으로 표시됩니다.

 

 

5. Boot Dashboard 설정 및 서버 포트 변경

 

파일들이 많이 지면 Application.java 에서 실행하는게 귀찮아 질 수 있습니다.

그래서 Boot Dashboard에서 바로 실행을 할 수 있도록 설정을 합니다.

 

window > show view > Other > Other > Boot Dashboard 선택

 

포트 변경은 src/main/resources/application.properties 파일에서

server.port=8888

로 설정하면 톰캣이 8888번 포트로 기동됩니다.

+ Recent posts