프로젝트 진행 중 아래와 같은 CORS 에러가 발생하여 이에 대한 해결 방법을 정리했습니다.

Access to XMLHttpRequest at '외부 API URL' from origin '외부 API를 호출한 URL' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
CORS란?
Cross-Origin Resource Sharing의 약자로 도메인이 다른 외부 서버 자원에 접근했을 때, 허용된 방식이 아닌 경우 CORS 에러가 발생합니다.

CORS 를 허용하는 설정 방법이 3가지가 있는 데, 이중 두가지 방법에 대해서 잘 적용되지 않아 filter를 직접 등록하는 방법으로 구현했습니다.

이유는 모르겠는데 아래 방법으로 설정했을 경우 잘 적용되지 않았습니다.
1. WebMvcConfigurer 인터페이스 구현을 통한 addCorsMappings 메소드 추가
2. 컨트롤러에 @CrossOrigin 어노테이션 추가

그래서 Filter 등록 방식으로 구현해보고자 합니다.
테스트하는 스프링 부트 버전, JDK 버전은 다음과 같습니다.
JDK 17
springframework.boot 2.6.7

1. CorsFilter를 컴포넌트로 등록한다

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Order(Ordered.HIGHEST_PRECEDENCE)
@Component
public class CorsFilter implements Filter {

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException,
        ServletException {
        HttpServletRequest request = (HttpServletRequest)req;
        HttpServletResponse response = (HttpServletResponse)res;

        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Credentials", "true");
        response.setHeader("Access-Control-Allow-Methods", "*");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers",
            "Origin, X-Requested-With, Content-Type, Accept, authorization");

        if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
            response.setStatus(HttpServletResponse.SC_OK);
        } else {
            chain.doFilter(req, res);
        }
    }
}
response.setHeader("Access-Control-Allow-Origin", "*");
허용하고 싶은 도메인을 지정합니다. "*" 일 경우 모든 도메인에 대한 자원 접근을 허용합니다.

response.setHeader("Access-Control-Allow-Credentials", "true");
true로 설정하면 credentials를 이용한 요청을 처리할 수 있습니다.

response.setHeader("Access-Control-Allow-Methods", "*");
접근가능한 method를 지정합니다 (GET, POST, PATCH, PUT, DELETE)

response.setHeader("Access-Control-Max-Age", "3600");
preflight 요청에 대한 응답을 브라우저에서 얼마만큼 캐싱하고 있을지 설정할 때 사용합니다.

response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, authorization");
서버에서 허용하는 header의 key값을 정의합니다. 
src/main/resources/data/data.txt 파일이 존재할 경우 해당 파일에 값에 접근하고 싶을 때 다음과 같이 하면됩니다.
    @Test
    public void resource() throws IOException {
        ClassPathResource resource = new ClassPathResource("data/data.txt");
        Path path = Paths.get(resource.getURI());
        String key = Files.readString(path);

        logger.info("[TEST] key : {}", key);
    }

 

[TEST] data.txt : ABCDEFGHIJK

 

application.yml 이나 application.properties 파일에 DB의 비밀번호 또는 키값을 명시해두는 경우가 있습니다.
문제는 중요한 값들이 외부로 노출되어 보안에 심각한 문제를 초래할 수 있다는 점입니다.
이를 해결하기 위해 보안에 민감한 값들을 암호화시켜 저장해야 합니다.

 

1. Jsaypt(Java Simplified Encryption)

Jasypt는 특정 값을 암호화해주는 라이브러리 입니다. 자세한 내용은 아래 링크를 참조해주세요.
http://www.jasypt.org/

 

2. DB의 url, username, password를 암호화하기

테스트는 SpringBoot 2.2.2.RELEASE에서 진행했습니다.

1) pom.xml

...
        <dependency>
            <groupId>com.github.ulisesbocchio</groupId>
            <artifactId>jasypt-spring-boot-starter</artifactId>
            <version>3.0.3</version>
        </dependency>
...

 

2) JasyptConfig.java 작성

import org.jasypt.encryption.StringEncryptor;
import org.jasypt.encryption.pbe.PooledPBEStringEncryptor;
import org.jasypt.encryption.pbe.config.SimpleStringPBEConfig;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class JasyptConfig {

    @Bean(name = "jasyptStringEncryptor")
    public StringEncryptor stringEncryptor() {

        String key = "my_jasypt_key";
        PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
        SimpleStringPBEConfig config = new SimpleStringPBEConfig();
        config.setPassword(key); // 암호화할 때 사용하는 키
        config.setAlgorithm("PBEWithMD5AndDES"); // 암호화 알고리즘
        config.setKeyObtentionIterations("1000"); // 반복할 해싱 회수
        config.setPoolSize("1"); // 인스턴스 pool
        config.setProviderName("SunJCE");
        config.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator"); // salt 생성 클래스
        config.setStringOutputType("base64"); //인코딩 방식
        encryptor.setConfig(config);
        return encryptor;
    }
}
jasyptStringEncryptor로 Bean을 등록합니다. 이 이름은 application.yml의 jasypt bean으로 등록할 때 사용합니다.

 

3) DB 값들을 미리 암호화하기

application.yml에 암호화된 값을 적어주기 전에 미리 해당 값들을 암호화 해두어야 합니다.
@SpringBootTest
class JasyptApplicationTests {

    @Test
    void contextLoads() {
    }

    @Test
    void jasypt() {
        String url = "my_db_url";
        String username = "my_db_username";
        String password = "my_db_password";

        System.out.println(jasyptEncoding(url));
        System.out.println(jasyptEncoding(username));
        System.out.println(jasyptEncoding(password));
    }

    public String jasyptEncoding(String value) {

        String key = "my_jasypt_key";
        StandardPBEStringEncryptor pbeEnc = new StandardPBEStringEncryptor();
        pbeEnc.setAlgorithm("PBEWithMD5AndDES");
        pbeEnc.setPassword(key);
        return pbeEnc.encrypt(value);
    }

}
//암호화된 값
GE1npS9T7z/GOqwcRLBPMbUSGQzj+WgSNCFvH5PoU83Z6ljps9ninis4P+4WNxBNz/RNhOrmReM= GFHmfIqHmrTaME93hLZqgg==
oBMaRuMSZyJDmebXacU8Vg==

 

4) application.yml 설정

spring:
  datasource:
    url: ENC(GE1npS9T7z/GOqwcRLBPMbUSGQzj+WgSNCFvH5PoU83Z6ljps9ninis4P+4WNxBNz/RNhOrmReM=)
    username: ENC(GFHmfIqHmrTaME93hLZqgg==)
    password: ENC(oBMaRuMSZyJDmebXacU8Vg==)
    
jasypt:
  encryptor:
    bean: jasyptStringEncryptor
jasyptStringEncryptor를 jasypt bean으로 등록하고, 각 속성값에 ENC( 암호화 값 ) 형식으로 입력해줍니다. 

이후 spring boot를 구동하면 잘 작동되는 것을 볼 수 있습니다.

1. hateoas란

hateoas는 Hypermedia As The Engine Of Application State의 약자로 REST API의 필수 구성요소 중 한가지입니다.

특정 API 요청 시 리소스 정보를 받아 볼 수 있는데, 이때 리소스 정보 뿐 만 아니라 리소스에 대한 다양한 링크정보를 리소스정보와 함께 반환하는 것을 의미합니다.

예를 들어서 유저 정보 생성 시 생성된 유저의 정보를 반환받는데, 이때 유저의 상세정보, 수정, 설정 등.. 유저와 관련된 다양한 링크페이지를 함께 반환받을 수 있습니다.

다시말해서 hateoas를 적용하면 API 요청 시, Resource와 Links를 함께 반환 받을 수 있습니다.

 

2. spring boot hateoas 적용

간단하게 Member를 저장하는 API를 작성해보록 하겠습니다.
DB를 별도로 사용하지 않았으며 IdentityHashMap 클래스를 이용해서 Member를 Key,Value 형식으로 저장할 수 있도록 구현하였습니다.

1) pom.xml

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
            <scope>provided</scope>
        </dependency>

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

 

2) Member.java

public class Member {
    private Integer id;
    private String name;
    
    //setter,getter 생략

}

 

3) MemberModel.java

import com.fasterxml.jackson.annotation.JsonUnwrapped;
import org.springframework.hateoas.RepresentationModel;

public class MemberModel extends RepresentationModel<MemberModel> {

    public MemberModel(Member member) {
        this.member = member;
    }

    @JsonUnwrapped
    private final Member member;

}
hateoas 기능을 사용하려면 RepresentationModel 클래스를 상속받은 클래스를 구현해야합니다.
멤버변수로 hateoas를 적용할 클래스를 선언해주면 됩니다.

@JsonUnwrapped 어노테이션을 사용하면 member 반환 시 member depth를 없애고 member의 멤버변수를 바로 출 력 받을 수 있습니다.

 

4) MemberController.java

import org.springframework.hateoas.MediaTypes;
import org.springframework.hateoas.server.mvc.WebMvcLinkBuilder;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.net.URI;
import java.util.IdentityHashMap;
import java.util.Map;

import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;

@RestController
@RequestMapping(value = "/api/member", produces = MediaTypes.HAL_JSON_VALUE)
public class MemberController {

    private Map<Integer, Member> db = new IdentityHashMap<>();
    private Integer id = 1;

    @PostMapping
    public ResponseEntity createMember(@RequestBody Member member) {
        member.setId(id++);

        /*
            /api/member
        */
        WebMvcLinkBuilder listLink= linkTo(MemberController.class);
        
        /*
            /api/member/{id}
        */
        WebMvcLinkBuilder selfLink = listLink.slash(member.getId());

        //hateoas model 객체 생성
        MemberModel memberModel = new MemberModel(member);

        //list link
        memberModel.add(listLink.withRel("list"));

        //self link
        memberModel.add(selfLink.withSelfRel());

        //update link
        memberModel.add(selfLink.withRel("update"));

        return ResponseEntity.created(selfLink.toUri()).body(memberModel);
    }
}
POST /api/member 요청 시 HAL_JSON 방식으로 리소스를 반환합니다.

반환 정보는 생성된 Member 정보와 Member와 관련된 링크 정보로 구성됩니다.
링크 정보는 list, self, update 정보를 담고 있습니다.

( HAL이란 Hypertext Application Language의 약자로 JSON 또는 XML 코드 내의 외부 리소스에 대한 링크와 같은 하이퍼 미디어를 정의하기 위한 표준 규칙입니다. )

 

3. 테스트

Request
URL : POST http://localhost:8080/api/member

content-type : application/hal+json
body : {"name":"kim"}


Response
Body : 


 

위 예제는 기초적인 hateoas 라이브러리 기능만 사용했으며, 자세한 정보는 아래 링크를 확인해주세요.
https://docs.spring.io/spring-hateoas/docs/current/reference/html/#fundamentals.representation-models

1. H2 데이터베이스란?

H2는 자바 기반 오픈소스 RDBMS입니다. 주로 인 메모리 데이터베이스로 사용되며 기능 테스트 할 때 유용합니다.

인 메모리 데이터베이스는 휘발성 데이터베이스이기 때문에 컴퓨터를 종료하면 모든 데이터가 삭제됩니다.

H2 DB를 따로 설치할 수 있지만 build.gradle dependency로 추가하면 약 2MB크기의 라이브러리로 H2 DB를 사용할 수 있습니다.

지금부터 간단하게 hibernate를 이용해서 h2 db에 테이블을 생성해보겠습니다.

 

2. build.gradle

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    compileOnly 'org.projectlombok:lombok'
    runtimeOnly 'com.h2database:h2'
    annotationProcessor 'org.projectlombok:lombok'
}

 

3. application.yml

spring:
  datasource:
    url: jdbc:h2:mem:test;MODE=MariaDB;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
    username: sa
    password:
    driverClassName: org.h2.Driver
  jpa:
    database-platform: org.hibernate.dialect.H2Dialect
    hibernate:
      ddl-auto: update
    show-sql: true
    properties:
      hibernate:
        format_sql: true
  h2:
    console:
      enabled: true
      path: /h2-console

 

 

4. Member Entity 생성

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class Member {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    private String name;
    
    //getter, setter 생략
}

 

5. spring boot 실행

o.s.b.a.h2.H2ConsoleAutoConfiguration    : H2 console available at '/h2-console'. Database available at 'jdbc:h2:~/test'
o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [name: default]
org.hibernate.Version                    : HHH000412: Hibernate Core {5.4.9.Final}
o.hibernate.annotations.common.Version   : HCANN000001: Hibernate Commons Annotations {5.1.0.Final}
org.hibernate.dialect.Dialect            : HHH000400: Using dialect: org.hibernate.dialect.H2Dialect

Hibernate: 
    
    drop table member if exists
Hibernate: 
    
    create table member (
       id integer generated by default as identity,
        name varchar(255),
        primary key (id)
    )
spring boot가 실행되면서 h2가 로딩되는 것을 볼 수 있습니다.
그리고 hibernate에 의해서 member table이 생성되었습니다.

 

6. h2 console

http://localhost:포트번호/h2-console

위 url로 접속하면 아래와 같은 h2 로그인 화면이 나타납니다. 

DB 연결 후 왼쪽 메뉴에 hibernate를 이용해서 생선한 Member 테이블을 확인할 수 있고 필요에 따라서 직접 sql query를 입력할 수 있습니다.

 

1. Gmail SMTP Server

구글 계정만 있으면 Gmail SMTP Server를 통해 무료로 이메일을 전송할 수 있습니다.

Gmail SMTP Service 설정 정보는 다음과 같습니다.

 

 

2. pom.xml (dependency)

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

 

3. application.yml

spring :         
    mail :
        host : smtp.gmail.com
        port : 587
        username : 'your email'
        password : 'your password'
        properties :
            mail :
                smtp :
                    auth : true
                    starttls :
                        enable : true   

 

4. 이메일 전송 예제

1) MailTO.java

전송할 메일의 정보를 저장합니다.
public class MailTO {

    private String address;
    private String title;
    private String message;
    
    /*
    getter, setter 생략
    */
}

 

2). MailService.java

메일 전송기능을 모아둔 서비스 입니다.
sendMail 메소드에 MailTO 값을 넘겨서 메일 전송을 수행합니다.
import com.example.bamdule.model.TO.MailTO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Component;


@Component
public class MailService {

    @Autowired
    private JavaMailSender mailSender;

    public void sendMail(MailTO mail) {
        SimpleMailMessage message = new SimpleMailMessage();
        message.setTo(mail.getAddress());
//        message.setFrom(""); from 값을 설정하지 않으면 application.yml의 username값이 설정됩니다.
        message.setSubject(mail.getTitle());
        message.setText(mail.getMessage());

        mailSender.send(message);
    }

}

 

 3). MailController.java

메일을 전송하는 컨트롤러입니다. 
Get 방식으로 sendTestMail 메소드를 호출할 때 email 값을 query String으로 받아 해당 email로 테스트 메일을 전송합니다.
import com.example.bamdule.model.TO.MailTO;
import com.example.bamdule.mail.MailService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


@RestController
@RequestMapping("/mail")
public class MailController {

    @Autowired
    private MailService mailService;

    @GetMapping("/send")
    public MailTO sendTestMail(String email) {
        MailTO mailTO = new MailTO();

        mailTO.setAddress(email);
        mailTO.setTitle("밤둘레 님이 발송한 이메일입니다.");
        mailTO.setMessage("안녕하세요. 반가워요!");

        mailService.sendMail(mailTO);

        return mailTO;
    }
}

 

4) 메일 전송 테스트

http://localhost:8080/컨텍스트경로/mail/send?email=Bamdule5@gmail.com

org.springframework.mail.MailAuthenticationException: Authentication failed; nested exception is javax.mail.AuthenticationFailedException: 535-5.7.8 Username and Password not accepted. Learn more at 535 5.7.8 https://support.google.com/mail/?p=BadCredentials b13sm3972805pjl.38 - gsmtp
위 에러가 발생하는 경우 보안 수준이 낮은 앱의 액세스를 허용해 주어야 합니다.

5. 파일 첨부 및 이미지가 삽입된 이메일 전송하기

1) MailHandler.java

import java.io.File;
import java.io.IOException;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.FileSystemResource;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;

public class MailHandler {

    private final JavaMailSender sender;
    private final MimeMessage mimeMessage;
    private final MimeMessageHelper mimeMessageHelper;

    public MailHandler(JavaMailSender javaMailSender) throws MessagingException {
        this.sender = javaMailSender;
        this.mimeMessage = javaMailSender.createMimeMessage();
        this.mimeMessageHelper = new MimeMessageHelper(mimeMessage, true, "UTF-8");
    }

    public void setFrom(String fromAddress) throws MessagingException {
        mimeMessageHelper.setFrom(fromAddress);
    }

    public void setTo(String email) throws MessagingException {
        mimeMessageHelper.setTo(email);
    }

    public void setSubject(String subject) throws MessagingException {
        mimeMessageHelper.setSubject(subject);
    }

    public void setText(String text, boolean useHtml) throws MessagingException {
        mimeMessageHelper.setText(text, useHtml);
    }

    public void setAttach(String fileName, String path) throws MessagingException, IOException {
        File file = new ClassPathResource(path).getFile();
        FileSystemResource fsr = new FileSystemResource(file);

        mimeMessageHelper.addAttachment(fileName, fsr);
    }

    public void setInline(String fileName, String path) throws MessagingException, IOException {
        File file = new ClassPathResource(path).getFile();
        FileSystemResource fileSystemResource = new FileSystemResource(file);

        mimeMessageHelper.addInline(fileName, fileSystemResource);
    }

    public void send() {
        sender.send(mimeMessage);
    }
}
javaMailSender 객체를 생성자로 받습니다.
MimeMassageHelper를 생성해 Mail을 커스텀할 수 있습니다.

setAttach메소드를 이용해 파일 첨부를 하고, setInline 메소드를 이용해 메일 안에 이미지를 삽입할 수 있습니다.

 

2) MailService.java에 아래 메소드 추가

   public void sendMailWithFiles(MailTO mail) throws MessagingException, IOException {
        MailHandler mailHandler = new MailHandler(mailSender);

        mailHandler.setTo(mail.getAddress());
        mailHandler.setSubject(mail.getTitle());
        
        String htmlContent = "<p>" + mail.getMessage() + "<p> <img src='cid:google-logo'>";
        mailHandler.setText(htmlContent, true);
        mailHandler.setAttach("test.txt", "static/test.txt");
        mailHandler.setInline("google-logo", "static/google-logo.png");
        mailHandler.send();
    }
src/main/resources/static 폴더 아래에 google-log.png 파일과 test.txt 파일을 넣어주셔야 합니다.
파일 명은 원하는 대로 변경해도 됩니다.

 

3) MailController.java에 아래 메소드 추가

    @GetMapping("/fileSend")
    public MailTO sendTestFileEmail(String email) throws MessagingException, IOException {
        MailTO mailTO = new MailTO();

        mailTO.setAddress(email);
        mailTO.setTitle("밤둘레 님이 발송한 이메일입니다.");
        mailTO.setMessage("안녕하세요. 반가워요!");

        mailService.sendMailWithFiles(mailTO);

        return mailTO;
    }

 

4) 테스트 메일 전송

http://localhost:8080/컨텍스트경로/mail/fileSend?email=Bamdule5@gmail.com


도움이 되셨다면 공감 한번씩 눌러주세요!

참조
https://victorydntmd.tistory.com/342?category=764331
05-Apr-2021 15:38:25.517 INFO [java-sdk-http-connection-reaper] org.apache.catalina.loader.WebappClassLoaderBase.checkStateForResourceLoading Illegal access: this web application instance has been stopped already. Could not load [ch.qos.logback.classic.spi.ThrowableProxy]. The following stack trace is thrown for debugging purposes as well as to attempt to terminate the thread which caused the illegal access.
 java.lang.IllegalStateException: Illegal access: this web application instance has been stopped already. Could not load [ch.qos.logback.classic.spi.ThrowableProxy]. The following stack trace is thrown for debugging purposes as well as to attempt to terminate the thread which caused the illegal access.
        at org.apache.catalina.loader.WebappClassLoaderBase.checkStateForResourceLoading(WebappClassLoaderBase.java:1372)
        at org.apache.catalina.loader.WebappClassLoaderBase.checkStateForClassLoading(WebappClassLoaderBase.java:1360)
        at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1219)
        at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1180)
        at ch.qos.logback.classic.spi.LoggingEvent.<init>(LoggingEvent.java:119)
        at ch.qos.logback.classic.Logger.buildLoggingEventAndAppend(Logger.java:419)
        at ch.qos.logback.classic.Logger.filterAndLog_0_Or3Plus(Logger.java:383)
        at ch.qos.logback.classic.Logger.log(Logger.java:765)
        at org.apache.commons.logging.LogAdapter$Slf4jLocationAwareLog.debug(LogAdapter.java:470)
        at com.amazonaws.http.IdleConnectionReaper.run(IdleConnectionReaper.java:190)
위와 같은 오류가 발생 할 경우 tomcat destroy 시 logback을 종료시키는 코드를 넣어주어야 합니다.
import ch.qos.logback.classic.LoggerContext;
import org.slf4j.LoggerFactory;

public class ShutdownHookConfiguration {

    public void destroy() {

        LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
        loggerContext.stop();

    }
}
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class MyApplication {

    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }

    @Bean(destroyMethod = "destroy")
    public ShutdownHookConfiguration shutdownHookConfiguration() {
        return new ShutdownHookConfiguration();
    }

}

 

spring boot를 사용하다보면 명시적으로 자원을 close 해주어야 하는 경우가 있습니다.
이때 spring boot destory event 발생 시 자원을 close해주는 코드를 추가해주면 됩니다.
public class ShutdownHookConfiguration {

    public void destroy() {
    /* close 로직*/
    }
}
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class MyApplication {

    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }

    @Bean(destroyMethod = "destroy")
    public ShutdownHookConfiguration shutdownHookConfiguration() {
        return new ShutdownHookConfiguration();
    }

}
ShutdownHookConfiguration 클래스를 생성하고 destroy 메소드를 선언한 후 close 로직을 작성합니다.
그런 다음 SpringBootAplication 어노테이션을 등록한 클래스에 ShutdownHookConfiguration 클래스를 Bean으로 등록한 다음 destroyMethod에 destory메소드를 등록해줍니다.

+ Recent posts