반응형

데코레이터 패턴(Decorator Pattern)

  - 데코레이터 패턴은 프록시 기법을 사용하는 디자인 패턴 중에 하나입니다.

  - GOF 디자인 패턴에서는 의도에 따라 두 가지 패턴으로 구분하였습니다. 

 

프록시의 주요 기능

프록시를 통해서 할 수 있는 일은 크게 2가지로 나뉜다.

1. 접근 제어 (권한에 따른 접근 차단, 캐싱 등)

2. 부가 기능 추가 (다른 로직을 추가하는 등의 부가 기능을 수행, 예) 실행 시간을 측정해서 로그를 남긴다.)

 

* GOF 디자인 패턴에서는 의도에 따라서 프록시 패턴과, 데코레이터 패턴으로 구분한다.

  - 프록시 패턴 : 접근 제어가 목적

  - 데코레이터 패턴 : 새로운 기능 추가가 목적

 

프록시 패턴의 장점

  - 부가 기능을 추가하여서 원하는 흐름에 맞게 조정할 수 있습니다.

 

코드

 * 메시지를 출력하는 기능에 메시지를 꾸며주는 기능을 데코레이터 패턴을 이용해서 적용해 보겠습니다.

 

Component.java

  - interface로 Component 생성

 

public interface Component {
    String operation();

}

 

RealComponent.java

  - 실제 Component 구현체

 

@Slf4j
public class RealComponent implements Component{
    @Override
    public String operation() {
        log.info("RealComponent 실행");
        return "data";
    }
}

 

DecoratorPatternClient.java

  - Component를 실행하는 클라이언트 클래스

 

@Slf4j
public class DecoratorPatternClient {

    private Component component;

    public DecoratorPatternClient(Component component) {
        this.component = component;
    }

    public void execute() {
        String result = component.operation();
        log.info("result={}", result);
    }

}

 

DecoratorPatternTest.java

  - 기본 메시지 출력 기능 테스트

@Slf4j
public class DecoratorPatternTest {

    @Test
    void noDecorator() {
        Component realComponent = new RealComponent();
        DecoratorPatternClient client = new DecoratorPatternClient(realComponent);
        client.execute();
    }
}

결과 : RealComponent 실행

          result=data

 

MessageDecorator.java

  - 데코레이터 패턴을 이용해서 메시지 꾸며주는 기능 추가

  - MessageDecorator는 Component 인터페이스를 구현한다.

 

@Slf4j
public class MessageDecorator implements Component{

    private Component component;

    public MessageDecorator(Component component) {
        this.component = component;
    }

    @Override
    public String operation() {
        log.info("MessageDecorator 실행");

        String result = component.operation();
        String decoResult = "<< =====" + result + " ===== >>";
        log.info("MessageDecorator 꾸미기 적용 전={}, 적용 후 ={}",result, decoResult);
        return decoResult;
    }
}

 

DecoratorPatterTest.java

  - 클라이언트가 호출하기 전에 MessageDecorator 클래스를 이용해서 부가 기능을 추가 후 클라이언트에게 messageDecorator를 넘겨준다. messageDecorator도 Component를 구현하고 있기 때문에 클라이언트 입장에서는 전혀 문제가 발생하지 않는다.

 

@Slf4j
public class DecoratorPatternTest {

    @Test
    void decorator1() {
        Component realComponent = new RealComponent();
        Component messageDecorator = new MessageDecorator(realComponent);
        DecoratorPatternClient client = new DecoratorPatternClient(messageDecorator);
        client.execute();
    }
}

 

결과 :

 

정리 :

 

데코레이터 패턴의 의도 : 객체에 추가 책임(기능)을 동적으로 추가하고, 기능 확장을 위한 유연한 대안 제공

프록시 패턴의 의도 : 다른 객체에 대한 접근을 제어하기 위해 대리자를 제공

728x90
반응형
반응형

전략 패턴

탬플릿 메서드 패턴은 부모 클래스에 변하지 않는 템플릿을 두고, 변하는 부분을 자식 클래스에 두어서 상속을 사용해서 문제를 해결하였습니다.
전략 패턴은 변하지 않는 부분을 Context 라는 곳에 두고, 변하는 부분을 Strategy 라는 인터페이스를 만들고 해당 인터페이스를 구현하도록 해서 문제를 해결합니다.

상속이 아니라 위임으로 문제를 해결하는 것이다.

전략 패턴에서 Context 는 변하지 않는 템플릿 역할을 하고, Strategy 는 변하는 알고리즘 역할을 합니다.

 

전략 패턴 의도

알고리즘 제품군을 정의하고 각각을 캡슐화하여 상호 교환 가능하게 만들자.

전략을 사용하면 알고리즘을 사용하는 클라이언트와 독립적으로 알고리즘을 변경할 수 있습니다.

 

코드

방식1. 필드에 전략을 보관하는 방식

 ContextV1.java

 

@Slf4j
public class ContextV1 {

    private Strategy strategy; // 전략을 필드 변수로 선언

    public ContextV1(Strategy strategy) {
        this.strategy = strategy;
    }

    public void execute() {
        long startTime = System.currentTimeMillis();
        //비지니스 로직 실행
        strategy.call(); //위임
        //비즈니스 로직 종료
        long endTime = System.currentTimeMillis();
        long resultTime = endTime - startTime;
        log.info("resultTime={}", resultTime);
    }
}

 

변하는 알고리즘

 StrategyLogin1.java

 

@Slf4j
public class StrategyLogin1 implements Strategy {

    @Override
    public void call() {
        log.info("비즈니스 로직1 실행");
    }
}

 

 StrategyLogin2.java

 

@Slf4j
public class StrategyLogin2 implements Strategy {

    @Override
    public void call() {
        log.info("비즈니스 로직2 실행");
    }
}

 

테스트 코드

ContextV1Test.java

 

@Slf4j
public class ContextV1Test {

    @Test
    void strategyV1() {
        StrategyLogin1 strategyLogin1 = new StrategyLogin1();
        ContextV1 contextV1 = new ContextV1(strategyLogin1);
        contextV1.execute();

        StrategyLogin2 strategyLogin2 = new StrategyLogin2();
        ContextV1 contextV2 = new ContextV1(strategyLogin2);
        contextV2.execute();
    }

}

 

전략 패턴 사용은 선 조립, 후 실행으로 생각하면 보다 간단합니다.

변하지 않는 코드 ContextV1 객체에 변하는 알고리즘인 StrategyLogin1, StrategyLogin2를 생성자에 넣어서 조립하고, 호출이 필요한 시점에 execute()를 이용해서 실행합니다.
스프링 애플리케이션 개발 할 때 애플리케이션 로딩 시점에 의존관계 주입을 통해 필요한 의존관계를 모두 맺어두고
다음에 실제 요청을 처리하는 것과 같은 원리이다.

 

위와 같이 사용하게 되면 변하는 알고리즘인 전략 class가(StrategyLogin1, StrategyLogin2) 계속 생겨날 수밖에 없습니다.

이러한 점을 보완하기 위해서 익명 내부 클래스를 사용합니다.

 

익명 내부 클래스 사용

 

@Slf4j
public class ContextV1Test {

    @Test
    void strategyV2() {
        Strategy strategyLogic1 = new Strategy() {

            @Override
            public void call() {
                log.info("비즈니스 로직1 실행");
            }
        };
        ContextV1 contextV1 = new ContextV1(strategyLogic1);
        contextV1.execute();

        Strategy strategyLogic2 = new Strategy() {

            @Override
            public void call() {
                log.info("비즈니스 로직2 실행");
            }
        };
        ContextV1 contextV2 = new ContextV1(strategyLogic2);
        contextV2.execute();

    }

}

 

람다를 이용해서 보다 더 코드 수를 줄일 수 있습니다.

 

@Slf4j
public class ContextV1Test {

    @Test
    void strategyV3() {
        ContextV1 contextV1 = new ContextV1(() -> log.info("비즈니스 로직1 실행"));
        contextV1.execute();

        ContextV1 contextV2 = new ContextV1(() -> log.info("비즈니스 로직2 실행"));
        contextV2.execute();

    }

}

 

방식2. 필드에 전략을 보관하는 방식

 

전략을 파라미터로 전달 받는 방식, 실행할 때 마다 전략을 유연하게 변경할 수 있다.
단점 역시 실행할 때 마다 전략을 계속 지정해주어야 한다.

 

ContextV2.java

 

@Slf4j
public class ContextV2 {

    public void execute(Strategy strategy) {
        long startTime = System.currentTimeMillis();
        //비지니스 로직 실행
        strategy.call(); //위임
        //비즈니스 로직 종료
        long endTime = System.currentTimeMillis();
        long resultTime = endTime - startTime;
        log.info("resultTime={}", resultTime);
    }
}

 

테스트 코드

ContextV2Test.java

 

@Slf4j
public class ContextV2Test {


    /**
     * 전략 패턴 적용
     */
    @Test
    void strategyV1() {
        ContextV2 context = new ContextV2();
        context.execute(new StrategyLogin1());
        context.execute(new StrategyLogin2());
    }

    /**
     * 전략 패턴 익명 내부 클래스
     */
    @Test
    void strategyV2() {
        ContextV2 context = new ContextV2();
        context.execute(new Strategy() {
            @Override
            public void call() {
                log.info("비즈니스 로직 1 실행");
            }
        });
        context.execute(new Strategy() {
            @Override
            public void call() {
                log.info("비즈니스 로직 2 실행");
            }
        });
    }

    /**
     * 전략 패턴 익명 내부 클래스2, 람다
     */
    @Test
    void strategyV3() {
        ContextV2 context = new ContextV2();
        context.execute(() -> log.info("비즈니스 로직 1 실행"));
        context.execute(() -> log.info("비즈니스 로직 2 실행"));
    }

}

 

참고로 ContextV2 형식의 전략 패턴은 스프링 내부에서 템플릿 콜백 패턴이라고 부른다.

스프링에서 jdbcTemplate, RestTemplate, ~Template 처럼 다양한 템플릿 콜백 패턴이 사용된다.

 

 

참조

 - inflearn(스프링 핵심 원리 - 고급편, 김영한)

728x90
반응형
반응형

이번 시간에는 Spring Framework에서 동시성 문제를 해결해 줄 ThreadLocal에 대해서 알아보겠습니다.

스프링은 빈을 싱글톤으로 등록한다. 애플리케이션에 딱 1개만 존재한다는 뜻이다.

이렇게 하나만 있는 인스턴스에 하나의 필드에 여러 쓰레드가 동시에 접근하여 수정하게 되면 A 사용자가 B 사용자의 데이터를 보게 되는 사고가 발생될 수 있다. 아래 목차와 같이 순서대로 동시성 원인부터 해결 방안까지 알아보겠습니다. 


<목차>

1. 동시성 문제 발생하는 예시

2. ThreadLocal을 이용해서 동시성 문제 해결 방안

3. ThreadLocal의 주의사항

 


1. 동시성 문제 발생하는 예시

FieldService.java

  1. nameStore을 맴버변수로 선언

  2. nameStore 변수에 값을 저장하고 1초 뒤에 저장한 nameStore 출력

 

@Slf4j
public class FieldService {

    private String nameStore;

    public String logic(String name) {
        log.info("저장 name={} -> nameStore={}", name, nameStore);
        nameStore = name;
        sleep(1000);
        log.info("조회 nameStore = {}", nameStore);
        return nameStore;
    }

    private void sleep(int millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

 

FieldServiceTest.java

  1. threadA.setName("thread-A");   -- Thread 이름 설정

  2. Thread A 시작하고 2초 잠시 멈추기 때문에 동시성 발생 안함.

  3. Thread B 시작하고 sleep(2000) 안하면 쓰레드 끝나기 전에 테스트 코드가 종료되서 모든 로그가 출력이 안되서 추가해야 한다.

 

@Slf4j
public class FieldServiceTest {

    private FieldService fieldService = new FieldService();

    @Test
    void field() {
        log.info("main start");

        Runnable userA = () -> {
            fieldService.logic("userA");
        };
        Runnable userB = () -> {
            fieldService.logic("userB");
        };

        Thread threadA = new Thread(userA);
        threadA.setName("thread-A"); // 1. thread 이름 설정
        Thread threadB = new Thread(userB);
        threadB.setName("thread-B");

        threadA.start();
        sleep(2000); // 2. 동시성 문제 발생x
        threadB.start();

        sleep(2000); // 3. 메인 쓰레드 종료 대기
        log.info("main exit");
    }

    private void sleep(int millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

 

TestCode 결과

  1. thead-A, thread-B 위에서 설정한 이름

  2. 정상적으로 thread-A에 userA thread-B에 userB 출력

 

 

동시성 문제 발생

FieldServiceTest.java

  1. ThreadA가 시작하고 0.1초만에 ThreadB 접근

 

@Slf4j
public class FieldServiceTest {

    private FieldService fieldService = new FieldService();

    @Test
    void field() {
        log.info("main start");

        Runnable userA = () -> {
            fieldService.logic("userA");
        };
        Runnable userB = () -> {
            fieldService.logic("userB");
        };

        Thread threadA = new Thread(userA);
        threadA.setName("thread-A");
        Thread threadB = new Thread(userB);
        threadB.setName("thread-B");

        threadA.start();
        sleep(100); // 1. 동시성 문제 발생
        threadB.start();

        sleep(2000);
        log.info("main exit");
    }

    private void sleep(int millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

 

TestCode 결과

  1. thread-A에 조회할 때 그 전에 thread-B가 nameStore을 수정해서 동시성 문제 발생 

 

 

 


2. ThreadLocal을 이용해서 동시성 문제 해결 방안

ThreadLocal은 해당 쓰레드만 접근할 수 있는 별도의 특별한 내부 저장소이다.

 

ThreadLocalService.java

  1. String nameStore -> ThreadLocal<String> nameStore 변경

  2. nameStore.set() - 값 저장

      nameStore.get() - 값 조회

      nameStore.remove() - 값 제거

 

@Slf4j
public class ThreadLocalService {

    private ThreadLocal<String> nameStore = new ThreadLocal<>();

    public String logic(String name) {
        log.info("저장 name={} -> nameStore={}", name, nameStore.get());
        nameStore.set(name);
        sleep(1000);
        log.info("조회 nameStore = {}", nameStore.get());
        return nameStore.get();
    }

    private void sleep(int millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

 

ThreadLocalServiceTest.java

  1. FieldService만 ThreadLocalService로 수정

 

@Slf4j
public class ThreadLocalServiceTest {

    private ThreadLocalService threadLocal = new ThreadLocalService();

    @Test
    void field() {
        log.info("main start");

        Runnable userA = () -> {
            threadLocal.logic("userA");
        };
        Runnable userB = () -> {
            threadLocal.logic("userB");
        };

        Thread threadA = new Thread(userA);
        threadA.setName("thread-A");
        Thread threadB = new Thread(userB);
        threadB.setName("thread-B");

        threadA.start();
        sleep(100); // 동시성 문제 발생x
        threadB.start();

        sleep(2000); // 메인 쓰레드 종료 대기
        log.info("main exit");
    }

    private void sleep(int millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

 

TestCode 결과

  1. ThreadLocal이라는 별도의 쓰레드 저장소를 사용함으로써 동시성 문제를 해결하였습니다.

 

 


3. ThreadLocal의 주의사항

ThreadLocal 값을 사용 후 제거하지 않고 그냥 두면 WAS에서 쓰레드 풀을 사용하는 경우 심각한 문제가 발생할 수 있다.

보통 was는 쓰레드 생성 비용이 비싸기 때문에 쓰레드를 사용하면 반납하고 재사용하게 된다. 

그러므로 thead-A를 사용하고 쓰레드 값을 제거하지 않을 상태에서 반납 후 다른 사용자 HTTP 요청이 thead-A에 할당되면 thead-A 보관소에 그대로 값이 있어서 다른 사용자에게 잘못된 정보가 노출될 수 있다.

이런 문제를 예방하기 위해서는 사용자 요청이 끝날 때 쓰레드 로컬의 값을 ThreadLocal.remove()를 통해서 꼭 제거해야 한다.

* Spring Framework 같은 경우 Filter나 Interceptor를 이용해서 깔끔하게 제거해 주는 것이 안전하다.

 

 

 

참조

  - inflearn 강의(스프링 핵심 원리 - 고급편, 김영한)

728x90
반응형
반응형

토이 프로젝트로 혼자서 클라우드 서비스를 이용하여 웹 개발부터 배포까지 온 과정을 경험해 보았습니다.

이 과정을 단계별로 나누어서 정리해 보려고 합니다.!

많은 피드백은 감사합니다!

 

목차 

STEP 01) NCP 서버 

STEP 02) AWS RDS, S3

STEP 03) Web Application 개발

STEP 04) Jenkins pipeline 배포

STEP 05) Domain 등록

 

 


 

* Web Application 개발은 앞에서 살펴보았던 AWS 서비스를 사용하는 방법만 소개하도록 하겠습니다.

 

개발 환경

 - SpringBoot 2.6.6

 - Mariadb 2.7.5

 - Gradle 7.4.1

 

1. RDS 연결(yml 설정)

  - 이전 RDS 설정 확인

spring:
  datasource:
    url: jdbc:mariadb://rds end point 주소 + prot + schema name
      예) jdbc:mariadb://rds.amazonaws.com:3306/rdstest
    driver-class-name: org.mariadb.jdbc.Driver
    username: admin
    password: 패스워드

 

2. S3 연결

  - 이전 S3 설정 확인

1.  spring cloud starter 의존성 추가

  - build.gradlew

implementation 'io.awspring.cloud:spring-cloud-starter-aws:2.3.1'

 

2. yml 설정

cloud:
  aws:
    credentials:
      access-key: access key
      secret-key: secret key
    s3:
      region: ap-northeast-2
      endpoint: s3-bucket

 

  - access key, secrey key 새 액세스 키 만들기로 생성

 

 

- region, endpoint 작성

  endpoint는 :::뒤에 복/붙

 

AwsS3Config.java

 

package com.bumblebee.dailyspecial.domain.aws;

import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author bumblebee
 */
@Configuration
public class AwsS3Config {

    @Value("${cloud.aws.credentials.access-key}")
    private String accessKey;

    @Value("${cloud.aws.credentials.secret-key}")
    private String secretKey;

    @Value("${cloud.aws.s3.region}")
    private String region;

    @Bean
    public AmazonS3Client amazonS3Client() {
        BasicAWSCredentials awsCreds = new BasicAWSCredentials(accessKey, secretKey);
        return (AmazonS3Client) AmazonS3ClientBuilder.standard()
                .withRegion(region)
                .withCredentials(new AWSStaticCredentialsProvider(awsCreds))
                .build();
    }

}

 

AwsS3Service.java

 

package com.bumblebee.dailyspecial.domain.aws;

import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.model.CannedAccessControlList;
import com.amazonaws.services.s3.model.ObjectMetadata;
import com.amazonaws.services.s3.model.PutObjectRequest;
import com.bumblebee.dailyspecial.domain.comutils.CommonUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.io.InputStream;

/**
 * @author bumblebee
 */
@Slf4j
@RequiredArgsConstructor
@Service
public class AwsS3Service {

    private final AmazonS3Client amazonS3Client;

    @Value("${cloud.aws.s3.endpoint}")
    private String bucketName;

    public String uploadFileV1(String category, MultipartFile multipartFile) {
        validateFileExists(multipartFile);

        String fileName = CommonUtils.buildFileName(category, multipartFile.getOriginalFilename());

        ObjectMetadata objectMetadata = new ObjectMetadata();
        objectMetadata.setContentType(multipartFile.getContentType());

        try (InputStream inputStream = multipartFile.getInputStream()) {
            amazonS3Client.putObject(new PutObjectRequest(bucketName, fileName, inputStream, objectMetadata)
                    .withCannedAcl(CannedAccessControlList.PublicRead));
        } catch (IOException e) {
//            throw new FileUploadFailedException();
        }

        return amazonS3Client.getUrl(bucketName, fileName).toString();
    }

    private void validateFileExists(MultipartFile multipartFile) {
        if (multipartFile.isEmpty()) {
//            throw new EmptyFileException();
        }
    }

}

 

AwsS3Config class에 amazonS3Client 메소드를 @Bean으로 등록합니다.

파일을 업로드가 필요한 로직에 AwsS3Service class에서 uploadFIleV1 메소드를 이용해서 S3에 업로드 합니다.

예)

awsS3Service.uploadFileV1("Img", multipartFile);

 

그 외 다운로드 및 다중 업로드 기능도 제공하고 있습니다.

 

참조

  - Springboot로 S3 파일 업로드하기

728x90
반응형
반응형

 

SpringBoot에서 쉽고 빠르게 Interceptor를 적용하는 방법을 알아보겠습니다.

스프링 인터셉터도 서블릿 필터와 같이 웹과 관련된 공통 관심 사항을 효과적으로 해결할 수 있는 기술입니다.

서블릿 필터가 서블릿이 제공하는 기술이라면, 스프링 인터셉터는 스프링 MVC가 제공하는 기술입니다.

둘다 웹과 관련된 공통 관심 사항을 처리하지만, 적용되는 순서와 범위, 그리고 사용방법이 다릅니다.

 

Spring에서 서블릿 필터를 적용하고 싶다면 아래 링크 클릭!

[Spring Filter 적용하기]

인터셉터 흐름

HTTP 요청 -> WAS -> 필터 -> 서블릿(dispatcherservlet) -> 스프링 인터셉터 -> 컨트롤러

인터셉터 특징

 1. 스프링 인터셉터에 URL 패턴을 적용할 수 있는데, 서블릿 URL 패턴과는 다르고, 매우 정밀하게 설정할 수 있습니다.

 2. 서블릿 필터의 경우 단순하게 doFilter() 하나만 제공되지만 인터셉터는 컨트롤러 호출 전(preHandle), 호출 후(postHandle), 요청 완료 이후(afterCompletion)와 같이 단계적으로 잘 세분화 되어 있다.

  ** (예외 발생 시 postHandle은 실행이 안되고, preHandle, afterCompletion만 실행된다.)

 3. 서블릿 필터의 경우 단순히 request , response 만 제공했지만, 인터셉터는 어떤 컨트롤러( handler )가 호출되는지 호출 정보도 받을 수 있다. 그리고 어떤 modelAndView 가 반환되는지 응답 정보도 받을 수 있다.

 

소스 코드

TestInterceptor.java

  - HadlerInterceptor를 구현하면 Spring Interceptor를 사용할 수 있다.

 

package com.bumblebee.dailyspecial.domain.interceptor;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Slf4j
public class TestInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.info("preHandle");
        String requestURI = request.getRequestURI();
        log.info(requestURI);
        return true; // return true면 정상 호출, 다음 인터셉터나 컨트롤러 호출한다.
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        log.info("postHandle");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        log.info("afterCompletion");
    }
}

 

위에서 작성한 인터셉터를 등록해줘야 합니다.

WebConfig.java

 

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {

        registry.addInterceptor(new TestInterceptor())
                .order(1) // Interceptor 적용 순서
                // filter와 다르게 매우 정밀하게 url 패턴 적용이 가능하다.
                .addPathPatterns("/**") // 적용될 url 패턴
                .excludePathPatterns("/css/**", "/*.ico", "/error"); //제외될 url 패턴
    }
}

 

TestInterceptor 출력 화면

 

 

 

 ** Interceptor 주의 사항

서블릿 필터의 경우 (init, doFilter, destroy) 메소드에서 공유할 변수를 지역변수로 해결이 가능하지만,

스프링 인터셉터는 호출 시점이 완전히 분리되어 있습니다.

따라서 preHandle 에서 지정한 값을 postHandle , afterCompletion 에서 함께 사용하려면 어딘가에 담아두어야 합니다. TestInterceptor 도 싱글톤 처럼 사용되기 때문에 맴버변수를 사용하면 위험합니다.

따라서 request 에 담아두었다가( request.setAttribute(test, 123) ).

이 값은 afterCompletion 에서 request.getAttribute(test) 로 찾아서 사용하면 됩니다.

 

728x90
반응형
반응형

SpringBoot에서 쉽고 빠르게 Filter를 적용하는 방법을 알아보겠습니다.

FIlter는 서블릿이 지원하는 수문장이다.

 

필터 흐름

HTTP 요청 -> WAS -> 필터 -> 서블릿(Spring에서는 dispatcherservlet) -> 컨트롤러

 

필터 특징

1. 특정 URL 패턴에 적용할 수 있다.(/* 모든 요청 적용)

2. 필터 체인을 이용해서 필터를 자유롭게 추가할 수 있다.

 

소스 코드

TestFilter.java

  - Spring에서 Filter는 Filter interface를 구현하면 사용할 수 있다.

  - Filter interface를 구현하게 되면 init, doFilter, destory 3가지 메소드를 Override 하게 된다.(doFilter 메소드는 반드시 구현되어야 한다.)

 

package com.bumblebee.dailyspecial.domain.filter;

import lombok.extern.slf4j.Slf4j;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

@Slf4j
public class TestFilter implements Filter {


    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        log.info("TestFilter init");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        
        log.info("TestFilter doFilter");
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        
        log.info(httpRequest.getRequestURI());
        chain.doFilter(request, response); 
        // 이 부분이 가장 중요하다. 
        //다음 필터가 있으면 필터를 호출하고, 필터가 없으면 서블릿을 호출한다. 
        //만약 이 로직을 호출하지 않으면 다음 단계로 진행되지 않는다.
    }

    @Override
    public void destroy() {
        log.info("TestFilter init");
    }
}

 

init(): 필터 초기화 메서드, 서블릿 컨테이너가 생성될 때 호출된다.
doFilter(): HTTP 요청이 올 때 마다 해당 메서드가 호출된다. 필터의 로직을 구현하면 된다.

destroy(): 필터 종료 메서드, 서블릿 컨테이너가 종료될 때 호출된다.

 

위에서 작성한 Filter를 등록해준다.

  - WebMvcConfigurer 구현해서 FilterRegistrationBean으로 필터를 등록해준다.

 

package com.bumblebee.dailyspecial.config;

import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import javax.servlet.Filter;

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Bean
    public FilterRegistrationBean TestFilter() {
        // SpringBoot 에서는 FilterRegistrationBean을 이용해서 필터 설정(was 올릴 때 서블릿 컨테이너 올릴 때 알아서 등록을 해준다.)
        FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
        filterRegistrationBean.setFilter(new TestFilter()); // 등록할 필터
        filterRegistrationBean.setOrder(1); // 필터 순서
        filterRegistrationBean.addUrlPatterns("/*"); // 필터 적용할 url 패턴

        return filterRegistrationBean;
    }


}

 

TestFilter 출력 화면

was 기동 후 init 메소드 호출
HTTP 요청 후 doFilter 메소드 호출
was 종료 후 destroy 메서드 호출

 

728x90
반응형
반응형

토이 프로젝트로 혼자서 클라우드 서비스를 이용하여 웹 개발부터 배포까지 온 과정을 경험해 보았습니다.

이 과정을 단계별로 나누어서 정리해 보려고 합니다.!

많은 피드백은 감사합니다!

 

목차 

STEP 1) NCP 서버 

STEP 2) AWS RDS, S3

STEP 3) Web Application 개발

STEP 04) Jenkins pipeline 배포

STEP 05) Domain 등록


AWS(Amazon Web Services)

  - 아마존 클라우드 서비스

  - 현재 클라우드 컴퓨팅 분야에서 세계 1위

  - 다양한 클라우드 자원을 1년간 무료로 경험해 볼 수 있다.

  - 많은 국내 IT 기업에서도 사용 중이다.

 

* 이번 장에서는 Web Application를 만드는데 필요한 객체 스토리지 서비스(S3)를 AWS에서 사용하는 방법을 알아보겠습니다. 

S3

S3는 AWS(Amazon Web Service)에서 제공하는 인터넷 스토리지 서비스입니다.
S3(Simple Storage Service) 를 뜻합니다.

 

장점

  • 높은 내구도를 자랑하며 정보를 안전하게 저장 할 수 있습니다.
  • 저렴한 비용으로 사용이 가능합니다(ec2에 이미지, 영상등을 저장하며 비용이 만만치 않습니다.)
  • 보안성이 뛰어납니다 ( SSL을 통하여 데이터 전송과 암호화를 하므로 해킹 걱정이 적습니다.)
  • 속도가 빠릅니다 (각 지역에 맞게 선택하며, 업/다운로드 시 지역시간 최소화를 위한 멀티 파트 업로드를 지원합니다.)

 

1. Amazon S3에서 버킷 만들기를 선택합니다.

 

 

2. S3 버킷 만들기

  - 버킷 이름과 AWS 리전을 선택합니다.

 

3. ACL 활성화됨을 선택합니다.

  - 비활성화를 하게 되면 개발 당시 파일 업로드할 때 400 에러가 발생된 경험이 있습니다.

 

4. 퍼블릭 액세스 차단은 맨 마지막 임의의 퍼블릭 버킷 또는 엑세스 지점 정책을 ... 제외하고 체크 하도록 하겠습니다.

  - 모든 퍼블릭 액세스 차단을 권장하고 있습니다.

 

5.  버킷 버전 관리 및 기본 암호화 운영할 때는 활성화를 하는 게 유지 보수 측면에서나 보안상 좋습니다.

 

6. 생성된 버킷에 이미지를 업로드해 보겠습니다.

 

7. 파일 추가 후 업로드 클릭

 

 

8. 객체 URL 복사 후 접근!

 

 

AccessDenied 에러 발생..

 

9. 버킷 > 권한 탭 > 버킷 정책 설정

  9.1 ) 퍼블릭 액세스 차단 모두 해체 후 저장

 

  9.2 ) 버킷 정책 편집 > 정책 생성기

 

 

  - Select Type of Policy : S3 Bucket Policy 

  - Principal : *

  - Actions : GetObject

  - Amazon Resource Name(ARN) : 버킷 > 속성 탭 > Amazon 리소스 이름(ARN) /* (이름 뒤에 /* 붙여줍니다.)

  9.3 ) Generate Policy 후 JSON 복사 > 버킷 정책에 붙여넣기 후 저장

 

10. 객체 URL 접근 이미지 출력!

 

728x90
반응형
반응형

토이 프로젝트로 혼자서 클라우드 서비스를 이용하여 웹 개발부터 배포까지 온 과정을 경험해 보았습니다.

이 과정을 단계별로 나누어서 정리해 보려고 합니다.!

많은 피드백은 감사합니다!

 

목차 

STEP 01) NCP 서버 

STEP 02) AWS RDS, S3

STEP 03) Web Application 개발

STEP 04) Jenkins pipeline 배포

STEP 05) Domain 등록


AWS(Amazon Web Services)

  - 아마존 클라우드 서비스

  - 현재 클라우드 컴퓨팅 분야에서 세계 1위

  - 다양한 클라우드 자원을 1년간 무료로 경험해 볼 수 있다.

  - 많은 국내 IT 기업에서도 사용 중이다.

 

* 이번 장에서는 Web Application를 만드는데 필요한 Database(RDS)를 AWS에서 사용하는 방법을 알아보겠습니다. 

RDS

Amazon Relational Database Service(RDS)를 사용하면 클라우드에서 관계형 데이터베이스를 간편하게 설정, 운영 및 확장할 수 있습니다. 시간 소모적인 데이터베이스 관리 작업을 관리하는 한편, 효율적인 비용으로 크기를 조정할 수 있는 용량을 제공하므로 사용자는 애플리케이션과 비즈니스에 좀 더 집중할 수 있습니다.

장점

  • 관리 부담 감소 사용 편의성 ...
  • 성능 범용(SSD) 스토리지 ...
  • 확장성 즉각적인 컴퓨팅 규모 조정 ...
  • 가용성 및 내구성 자동 백업 ...
  • 보안 저장 데이터 및 전송 데이터 암호화 ...
  • 관리 효율성 모니터링 및 지표 ...
  • 비용 효율성 사용한 만큼만 비용 지불

[AWS RDS 생성 방법]

1. 왼쪽 상단에 서울 region를 선택하고 데이터베이스 생성을 클릭합니다.

  - AWS는 여러 region이 있지만 아무래도 가장 가까운 region를 선택하는 게 네트워크 속도상 이점이 있습니다.

 

 

2. 아래와 같이 프리 티어로 MariaDB를 생성하도록 하겠습니다.

  - DB 인스턴트 식별자 : 생성할 DB의 이름이라고 생각하시면 됩니다.

  - 마스터 사용자 이름 / 마스터 암호 : DB 접속에 필요한 관리자 ID, PW입니다.

 

 

3. 생성하고 조금 기다리게 되면 아래와 같이 정말 쉽게 데이터베이스가 생성되었습니다.

 

 

4.  생성된 db를 클릭하게 되면 아래와 같이 여러 정보들을 확인 할 수 있습니다.

  - 모니터링을 통해서 cpu나 스토리지 상황을 확인 할 수 있고, 로그 및 이벤트 탭에서 이벤트 발생된 로그를 확인 할 수 있습니다.

 

 

5. DB가 생성되었으니 접속을 한 번 해보도록 하겠습니다.

  - DBeaver라는 DB tool를 이용해서 접속 테스트를 진행하였습니다.

  - Server Host에 위에서 생성한 DB의 엔드포인트를 작성하고 마스터 계정으로 접속 테스트를 합니다.

 

 

Test Connection 실행 화면

 

AWS RDS를 이용해서 몇 분 만에 손쉽게 데이터베이스를 생성해서 접속해 보았습니다.

다음으로 AWS S3를 생성하는 방법을 알아보겠습니다.

 

728x90
반응형

+ Recent posts