프로젝트하며 까먹었던 것들 정리하기

·2024년 7월 20일
0

SpringBoot

목록 보기
6/6

1. SpringBoot에서의 CORS 정책 설정

CORS: Cross Origin Resource Sharing

CORS 정책이란, 보안 상 문제를 위해 출처가 다른 두 도메인간의 통신을 위해선 별도로 통신 규약을 설정해줘야하는데, 이를 CORS정책이라 한다.

예를들면 개발환경에서 프론트는 로컬호스트는 3000포트고... 스프링의 기본 로컬호스트 포트는 8080아닌가?

근데 이 두 포트번호는 서로 다른 위치이기 때문에, 직접적으로 이 둘을 잇기 위해서는 CORS정책 설정이 필요하다.

위 설명에서 유추할 수 있듯, CORS정책은 서버와 프론트 둘 중 하나에서 설정하면 된다.

프론트 단에서 CORS정책 설정하기

주로 개발환경에서 프론트에서 CORS정책을 설정해준다고 한다.
프론트단에서 CORS정책을 설정하기 위해서는, 미들웨어 (징검다리라고 생각하면 편하다!) 라이브러리가 필요하다.

npm install http-proxy-middleware

그 다음 config.js와 같은 위치에 setUpProxy.js를 넣어준다. (파일 대문자명 틀리면 안된다!)

setUpProxy.js

const { createProxyMiddleware } = require("http-proxy-middleware");

module.exports = function (app) {
    app.use(
        '/api',
        createProxyMiddleware({
            target: 'http://localhost:8080',
            changeOrigin: true,
        })
    );
}

위 말은 localhost:8080/api로 들어오고 나가는 요청들을 같은 Origin으로 속여준다는 말이다.
(쉽게 말해, 프론트단에 실제 요청을 보내거나 받기 전, localhost:3000/api 로 바꿔주겠다는 의미)

Spring(서버)단에서 CORS오류 해결하기

실제 배포 환경에서는 리액트 앱과 API 서버가 동일한 도메인이나 서브 도메인에 배포되는 경우가 많으므로, 배포 환경에서는 스프링에서 CORS오류를 해결해주는게 좋다.

그리고 서버에서 CORS정책을 설정해주면 리액트 뿐만이 아닌, 앱 등 다양한 환경에서 일일이 CORS정책을 설정해 줄 필요가 없으므로, 서버에서 한 번에 설정을 해주는게 편하다!

Config속성으로, Origin을 설정해주는 클래스를 하나 만들자.

WebConfig.java

package com.yongfill.server.global.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("http://localhost:3000")
                .allowCredentials(true)
                .maxAge(3600)
                .allowedMethods("*");
    }


}

이것만 넣어주면 끗.

참고로 최근 진행한 프로젝트에서는 JWT를 이용한 인증,인가가 들어갔기 때문에 allowCredentials를 넣어줬다.

2. Spring에서는 EntityManagerFactory가 대체 가능하다.

기존 JAVA에서는 ORM을 하기 위해서는
EntityManager이 필요했고, 이를 관리하기 위한 EntityManagerFactory가 필요했다.

아래 코드처럼, emf객체를 생성하여 emf를 통해 em을 생성하고,
DB연동 작업이 끝나면 em과 emf를 닫아줘야했다.

private static EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpa_config");

    public static EntityManagerFactory getEntityManagerFactory() {
        return emf;
    }

    public static EntityManager getEntityManager() {
        return getEntityManagerFactory().createEntityManager();
    }

    public static void closeEntityManager(EntityManager em) {
        if (em != null) {
            em.close();
        }
    }

    public static void closeEntityManagerFactory() {
        if (emf != null) {
            emf.close();
        }
    }

하지만 스프링에서는 이러한 작업을 @PersistContext라는 어노테이션으로 단축시킬 수 있다.

@PersisteContext
private EntityManager em; //끝!

3. AOP란?

관점 지향 프로그래밍의 약자로, 애플리케이션의 핵심적인 기능과 부가적인 기능을 분리해 Aspect라는 모듈로 만들어 설계하고 개발하는 방법임.

부가적인 기능?

로깅이 대표적인데, 핵심적인 비즈니스(예: Controller, Service) 단에 일일이 log를 찍지 않고, 부가적인 기능을 정의하는 클래스를 Aspect라는 모듈로 만들어 특정 시점 전후로 로그를 찍게 만들 수 있다!

package com.yongfill.server.global.aspect;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

@Component
@Slf4j
@Aspect
public class LogAOP {

    //domain 이하 api 패키지의 모든 메서드에서 진행
    @Pointcut("execution(* com.yongfill.server..api.*.*(..))")
    private void cut(){}


    @Before("cut()")
    public void logBefore(JoinPoint joinPoint){

        //메서드 정보 받기
        Method method = getMethod(joinPoint);
        log.info("======= method {} start ========", method.getName());

        //파라미터 받아오기
        Object[] args = joinPoint.getArgs();
        if(args.length<=0) log.info("no parameter");
        for(Object arg : args){
            log.info("parameter : {}", arg);
        }
    }

    @AfterReturning(value = "cut()", returning = "returnObj")
    public void afterReturnLog(JoinPoint joinPoint, Object returnObj) {
        // 메서드 정보 받아오기
        Method method = getMethod(joinPoint);

        if (returnObj != null) {
            log.info("return type = {}", returnObj.getClass().getSimpleName());
            log.info("return value = {}", returnObj);
        }

        log.info("======= method {} ends =======", method.getName());
    }

    // JoinPoint로 메서드 정보 가져오기
    private Method getMethod(JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        return signature.getMethod();
    }


}

4. Sl4j와 Log4j2 (+ Log4j2로 바꿔야 하는 이유)

sl4j에 내장되어있던 log4j v2의 경우,보안이슈가 발견되었음. 이후 sl4j에서 제공하는 logback은 별다른 이슈가 발견되지는 않았으나, log4j2가 이전 보안 이슈도 해결되었을 뿐만 아니라 성능도 더 좋음

5. @Profile로 테스트 환경에 따른 환경 설정

이 경우 프로젝트 초반 구상과 달리, 시간 제한으로 인해 TestCode를 작성하지 못했다. 일일이 postman으로 API를 날려 결과를 확인하는 식으로 했기 때문에....
별 다른 도움은 받지 못한 글...

https://engkimbs.tistory.com/entry/Spring-%EC%8A%A4%ED%94%84%EB%A7%81-Profile-%EC%96%B4%EB%85%B8%ED%85%8C%EC%9D%B4%EC%85%98%EC%9D%84-%ED%86%B5%ED%95%9C-%ED%99%98%EA%B2%BD-%EC%84%A4%EC%A0%95Spring-Environment-Configuration-Profile

6. EnumConverter가 필요한 이유?

Enum으로 객체를 저장할 경우, Enum 속성값보다는 그 Enum 객체의 이름이 더욱 가독성이 좋을 것이다.
이 경우 DB에 그대로 Enum 객체의 이름을 매핑해주는 무언가가 필요한데, 이를 위해 EnumConverter가 필요하다!

https://techblog.woowahan.com/2600/
여기서는 한 종류(ItemCateogry)에 대한 EnumConverter만 다뤘지만, 우리는 하나의 컨버터로 여러 종류의 Enum 을 우려먹기 위해.....

아예 제네릭 타입을 이용하여 만들어버렸다!

참고블로그
코드는 이 블로그를 참고했다!



import jakarta.persistence.AttributeConverter;

import java.util.Arrays;
import java.util.Objects;

public abstract class AbstractEnumNameConverter<T extends Enum<T> & EnumName<E>, E> implements AttributeConverter<T, E> {
    private final Class<T> clazz;

    public AbstractEnumNameConverter(Class<T> clazz) {
        this.clazz = clazz;
    }

    @Override
    public E convertToDatabaseColumn(T attribute) {
        return attribute.getName();
    }

    @Override
    public T convertToEntityAttribute(E dbData) {
        if (Objects.isNull(dbData)) {
            return null;
        }

        return Arrays.stream(clazz.getEnumConstants())
                .filter(e -> e.getName().equals(dbData))
                .findFirst()
                .orElseThrow(() -> new IllegalArgumentException("Unknown Enum Data: " + dbData));
    }
}

7. @RequestBody와 @ResponseBody를 쓰는 이유

-> 프론트단에서 JSON형식으로 데이터를 넘겨주게 되는데
별도의 후처리과정없이(JSON String에서 문자열 검색으로 일일이 속성을 찾는다던가)
JSON 하나만 받아도 알아서 속성을 쏙쏙 걸러낼 수 있도록 사용됨

8. Blob객체에서 이름을 추출하는 법

아쉽게도~ Blob객체에서 이름을 추출할 수는 없다. Blob으로 만들어버리기 전, File 형태일 때 메타 데이터를 받아와야한다~

profile
풀스택 호소인

0개의 댓글