Skip to content

Instantly share code, notes, and snippets.

@rolroralra
Last active August 3, 2022 05:58
Show Gist options
  • Save rolroralra/7a032ec80c7e8ee007fe15ddcb502634 to your computer and use it in GitHub Desktop.
Save rolroralra/7a032ec80c7e8ee007fe15ddcb502634 to your computer and use it in GitHub Desktop.
Spring

DispatchServlet Filter vs Interceptor

https://mangkyu.tistory.com/173


@Cacheable

https://www.baeldung.com/spring-cache-tutorial

https://shinsunyoung.tistory.com/50


@Retryable, @Recover

https://www.baeldung.com/spring-retry

https://jobc.tistory.com/197


@Async

https://www.hanumoka.net/2020/07/02/springBoot-20200702-sringboot-async-service/

https://velog.io/@gillog/Spring-Async-Annotation%EB%B9%84%EB%8F%99%EA%B8%B0-%EB%A9%94%EC%86%8C%EB%93%9C-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0


Spring Content-Type Annotation

https://junhyunny.github.io/information/spring-boot/javascript/content-type-and-spring-annotation/


SpringBoot Actuaotr (Version API)

[]https://nevercaution.github.io/spring-boot-actuator/](https://nevercaution.github.io/spring-boot-actuator/)


Spring Application Properties Document

https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html


SpringSecurity Filter

https://velog.io/@sa833591/Spring-Security-5-Spring-Security-Filter-%EC%A0%81%EC%9A%A9


DispatcherServlet in SpringBoot

https://www.baeldung.com/spring-boot-dispatcherservlet-web-xml


SpringBooServletInitializer

https://medium.com/@SlackBeck/spring-boot-%EC%9B%B9-%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98%EC%9D%84-war%EB%A1%9C-%EB%B0%B0%ED%8F%AC%ED%95%A0-%EB%95%8C-%EC%99%9C-springbootservletinitializer%EB%A5%BC-%EC%83%81%EC%86%8D%ED%95%B4%EC%95%BC-%ED%95%98%EB%8A%94%EA%B1%B8%EA%B9%8C-a07b6fdfbbde


Shed Lock

https://eunbc-2020.tistory.com/200


✅ Jasypt (Java Simplified Encryption)

https://velog.io/@sixhustle/Jasypt

Details

@Configuration
@EnableConfigurationProperties(JasyptConfigProperties.class)
@RequiredArgsConstructor
public class JasyptConfig {
    private final JasyptConfigProperties jasyptConfigProperties;

    @Bean(name = "jasyptStringEncryptor")
    public StringEncryptor stringEncryptor() {
        PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
        SimpleStringPBEConfig config = new SimpleStringPBEConfig();

        config.setPassword(System.getProperty("jasypt.encryptor.password"));
        config.setAlgorithm(jasyptConfigProperties.getAlgorithm());
        config.setKeyObtentionIterations(jasyptConfigProperties.getKeyObtentionIterations());
        config.setIvGeneratorClassName(jasyptConfigProperties.getIvGeneratorClassName());
        config.setPoolSize(jasyptConfigProperties.getPoolSize());
        config.setSaltGeneratorClassName(jasyptConfigProperties.getSaltGeneratorClassName());
        config.setStringOutputType(jasyptConfigProperties.getStringOutputType());
        encryptor.setConfig(config);
        return encryptor;
    }
}
@Component
@ConfigurationProperties(value = "jasypt.encryptor")
@Data
public class JasyptConfigProperties {
    private String algorithm = "PBEWITHHMACSHA512ANDAES_256";
    private String keyObtentionIterations = "1000";
    private String poolSize = "1";
    private String saltGeneratorClassName = "org.jasypt.salt.RandomSaltGenerator";
    private String stringOutputType = "base64";
    private String ivGeneratorClassName = "org.jasypt.iv.RandomIvGenerator";
}
bootRun {
	if ( project.hasProperty('jvmArgs') ) {
		jvmArgs = (project.jvmArgs.split("\\s+") as List)
	}
}		
jasypt:
  encryptor:
    bean: jasyptStringEncryptor
    password: password.          # VM Options을 통해서 실행하면 생략가능.
# Way1. Application run (Add VM Options)
$ java -jar xxx.jar -Djasypt.encrpytor.password=password
		
# Way2. gradle bootrun
$ gradle bootRun -PjvmArgs="-Djasypt.encryptor.password=password"


ConstraintValidator

@ConstraintValidator
@Constraint(validatedBy = ...)

https://cheese10yun.github.io/ConstraintValidator/


Spring Application Properties

https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html


logback-spring.xml

https://exhibitlove.tistory.com/302


Spring AOP vs AspectJ

https://logical-code.tistory.com/118


@JdbcTest, @DataJdbcTest, @DataJpaTest

https://pomo0703.tistory.com/100


Jdbc, JdbcTemplate, NamedParamterJdbcTemplate, JPA EntityManager, HibernateTemplate

https://github.com/rolroralra/hello-spring/tree/master/src/main/java/sample/dao/impl


HATEOS

Details

package com.example.controller;

import static org.springframework.hateoas.mvc.ControllerLinkBuilder.*;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.example.domain.Result;
import com.example.domain.Stock;
import com.example.service.StockService;


@RestController
@CrossOrigin()
@RequestMapping("/api/v1/stock")
public class StockController {
	private static final Logger LOGGER = LoggerFactory.getLogger(StockController.class);
	
	@Autowired
	private StockService stockService;
	
	@GetMapping("/alt1/{productId}")
	public ResponseEntity<Result<Stock>> findWithResponseEntity(@PathVariable String productId) {
		Result<Stock> result = new Result<Stock>();
		result.add(linkTo(methodOn(StockController.class).find(productId)).withRel("find"));
		result.add(linkTo(methodOn(StockController.class).register(result.getResult())).withRel("register"));
		result.add(linkTo(methodOn(StockController.class).modify(result.getResult())).withRel("modify"));
		result.add(linkTo(methodOn(StockController.class).remove(productId)).withRel("remove"));
		return new ResponseEntity<Result<Stock>>(result, HttpStatus.ACCEPTED);
	}
	
	@GetMapping("/{productId}")
	public Result<Stock> find(@PathVariable String productId) {
		LOGGER.info("find {}", productId);
		
		Result<Stock> result = new Result<>();
		 
        Stock stock = stockService.find(productId);
        if (stock == null) {
            result.setErrorCode(HttpStatus.NOT_FOUND.value());
            result.setErrorMessage("Not found.");
        } else {
            result.setResult(stock);
        }
 
        return result;
	}
	
	@PostMapping()
	public Result<String> register(@RequestBody Stock newStock) {
		LOGGER.info("register {}", newStock);
		
		Result<String> result = new Result<>();
		 
        Stock resultStock = stockService.register(newStock);
        if (resultStock == null) {
            result.setErrorCode(HttpStatus.BAD_REQUEST.value());
            result.setErrorMessage("Already Exists.");
        } else {
            result.setResult(resultStock.getProductId());
        }
 
        return result;
	}
	
	@PutMapping()
	public Result<String> modify(@RequestBody Stock newStock) {
		LOGGER.info("modify {}", newStock);
		
		Result<String> result = new Result<>();
		 
        Stock resultStock = stockService.modify(newStock);
        if (resultStock == null) {
            result.setErrorCode(HttpStatus.NOT_FOUND.value());
            result.setErrorMessage("Not found.");
        } else {
            result.setResult(resultStock.getProductId());
        }
 
        return result;
	}
	

	@DeleteMapping("/{productId}")
	public Result<Stock> remove(@PathVariable String productId) {
		LOGGER.info("remove {}", productId);
		
		stockService.remove(productId);
        return new Result<Stock>();
	}
}


Splunk, Sentry, Elasticsearch & Kibana (EFK)


SpEL (Spring Expression Language)

https://www.baeldung.com/spring-expression-language

https://www.concretepage.com/spring-5/spring-value-default#List


Spring WebSocket (STOMP)

Single Text Oriented Message Protocol

https://supawer0728.github.io/2018/03/30/spring-websocket/


Spring Redis (Cache)

https://yonguri.tistory.com/82


Spring Quartz (cf. Spring Schedular)

https://junhyunny.github.io/spring-mvc/quartz-in-spring-mvc/


Spring Security

https://mangkyu.tistory.com/76

https://mangkyu.tistory.com/77


Spring WebFlux (Reactive Programming)

https://www.youtube.com/watch?v=4x1QRyMIjGU

https://veluxer62.github.io/translate/spring-webflux/


Spring Thymeleaf

https://www.thymeleaf.org/doc/articles/standarddialect5minutes.html


SpringBoot Dockerization

https://spring.io/guides/gs/spring-boot-docker/


Servlet Filter vs Interceptor Handler vs AOP

https://goddaehee.tistory.com/154


Hamcrest (cf. Assertj)

https://escapefromcoding.tistory.com/248

Details

method description
allOf matches if all matchers match (short circuits)
anyOf matches if any matchers match (short circuits)
not matches if the wrapped matcher doesn’t match and vice
equalTov test object equality using the equals method
is decorator for equalTo to improve readability
hasToString test Object.toString
instanceOf, isCompatibleType test type
notNullValue, nullValue test for null
sameInstance test object identity
hasEntry, hasKey, hasValue test a map contains an entry, key or value
hasItem, hasItems test a collection contains elements
hasItemInArray test an array contains an element
closeTo test floating point values are close to a given value
greaterThan, greaterThanOrEqualTo, lessThan, lessThanOrEqualTo compare number
equalToIgnoringCase test string equality ignoring case
equalToIgnoringWhiteSpace test string equality ignoring differences in runs of whitespace
containsString, endsWith, startsWith test string matching


MockMVC JUnit Test

ApiDocumentUtil

package com.sds.nexledger.smartPass.controller.common;

import org.springframework.restdocs.operation.preprocess.OperationRequestPreprocessor;
import org.springframework.restdocs.operation.preprocess.OperationResponsePreprocessor;

import static org.springframework.restdocs.operation.preprocess.Preprocessors.*;

public interface ApiDocumentUtils {
    static OperationRequestPreprocessor getDocumentRequestPreprocessor() {
        return preprocessRequest(
                modifyUris()
                        .scheme("http")
                        .host("localhost")
                        .port(8080),
                prettyPrint());
    }

    static OperationResponsePreprocessor getDocumentResponsePreprocessor() {
        return preprocessResponse(prettyPrint());
    }
} 

CommonControllerTests

package com.sds.nexledger.smartPass.controller.common;

import io.restassured.RestAssured;
import io.restassured.builder.RequestSpecBuilder;
import io.restassured.specification.RequestSpecification;
import org.junit.Before;
import org.junit.Rule;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.restdocs.JUnitRestDocumentation;

import static org.springframework.restdocs.restassured3.RestAssuredRestDocumentation.documentationConfiguration;

public class CommonControllerTests implements ApiDocumentUtils {
    @Rule
    public JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation();

    @LocalServerPort
    int port;

    public RequestSpecification spec;

    @Before
    public void before() {
        this.spec = new RequestSpecBuilder()
                .addFilter(documentationConfiguration(this.restDocumentation))
                .build();

        RestAssured.baseURI = "http://localhost";
        RestAssured.port = this.port;
    }
}

MockMVC

package com.sds.nexledger.smartPass.controller;

import com.google.gson.Gson;
import com.google.gson.internal.LinkedTreeMap;
import com.google.gson.reflect.TypeToken;
import com.sds.nexledger.smartPass.controller.common.ApiDocumentUtils;
import com.sds.nexledger.smartPass.controller.dto.offchain.DataDTO;
import com.sds.nexledger.smartPass.controller.dto.offchain.DataRequestDTO;
import com.sds.nexledger.smartPass.controller.dto.offchain.OwnershipDTO;
import com.sds.nexledger.smartPass.controller.dto.offchain.OwnershipRequestDTO;
import com.sds.nexledger.smartPass.util.gson.DateDeserializer;
import lombok.extern.slf4j.Slf4j;
import org.junit.FixMethodOrder;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.runners.MethodSorters;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureWebMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders;
import org.springframework.restdocs.payload.JsonFieldType;
import org.springframework.restdocs.snippet.Attributes;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.ResultActions;

import java.util.ArrayList;
import java.util.List;
import java.util.StringJoiner;
import java.util.stream.IntStream;

import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.*;
import static org.springframework.restdocs.payload.PayloadDocumentation.*;
import static org.springframework.restdocs.request.RequestDocumentation.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;


@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
@AutoConfigureWebMvc
@AutoConfigureRestDocs
@ExtendWith(SpringExtension.class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Slf4j
public class ControllerMockMvcTests {
    @Autowired
    public MockMvc mockMvc;
    @Autowired
    public Gson gson;
    private final String BASE_URL = "/offchain/v1";
    private final String POSTFIX_REST_DOC = "_MockMvc";

//    @MockBean
//    public OffchainService offchainService;

    @AfterEach
    public void tearDown() {
    }

    @Test
    public void getDataAll() {
        try {
            String restDocumentName = "getDataAll" + POSTFIX_REST_DOC;
            // Given
//            Mockito.when(offchainService.getAllData()).thenReturn(Collections.singletonList(new DataDTO()));

            // When
            String url = BASE_URL+ "/data/all/metadata";
            ResultActions resultActions = mockMvc.perform(
                    get(url).accept(MediaType.APPLICATION_JSON)
            );

            // Then
            MvcResult mvcResult = resultActions
                    .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
                    .andExpect(status().isOk())
                    .andDo(document(
                            restDocumentName,
                            ApiDocumentUtils.getDocumentRequestPreprocessor(),
                            ApiDocumentUtils.getDocumentResponsePreprocessor(),
                            responseFields(
                                    fieldWithPath("[].dataId").type(JsonFieldType.STRING).description("dataId"),
                                    fieldWithPath("[].name").type(JsonFieldType.STRING).description("name"),
                                    fieldWithPath("[].type").type(JsonFieldType.STRING).description("type"),
                                    fieldWithPath("[].fileId").type(JsonFieldType.STRING).description("fileId"),
                                    fieldWithPath("[].description").type(JsonFieldType.STRING).description("description").optional(),
                                    fieldWithPath("[].hash").type(JsonFieldType.STRING).description("hash"),
                                    fieldWithPath("[].creator").type(JsonFieldType.STRING).description("creator"),
                                    fieldWithPath("[].updater").type(JsonFieldType.STRING).description("updater"),
                                    fieldWithPath("[].timestamp").type(JsonFieldType.STRING).attributes(Attributes.key("format").value(DateDeserializer.DEFAULT_DATE_FORMAT)).description("timestamp"),
                                    fieldWithPath("[].properties").type(JsonFieldType.OBJECT).description("properties").optional(),
                                    fieldWithPath("[].version").type(JsonFieldType.NUMBER).description("version"),
                                    fieldWithPath("[].status").type(JsonFieldType.STRING).description("status"),
                                    fieldWithPath("[].expirationTime").type(JsonFieldType.NUMBER).description("expirationTime").optional()))
                    )
                    .andReturn();

            // Return JSON type Check
            String responseString = mvcResult.getResponse().getContentAsString();
            List<DataDTO> list = gson.fromJson(responseString, TypeToken.getParameterized(List.class, DataDTO.class).getType());
            Assertions.assertNotNull(list);
        } catch (Throwable e) {
            Assertions.fail(e);
        }
    }
}

RestAssured

package com.sds.nexledger.smartPass.controller;

import com.google.gson.Gson;
import com.google.gson.internal.LinkedTreeMap;
import com.google.gson.reflect.TypeToken;
import com.sds.nexledger.smartPass.controller.common.ApiDocumentUtils;
import com.sds.nexledger.smartPass.controller.common.CommonControllerTests;
import com.sds.nexledger.smartPass.controller.dto.offchain.DataDTO;
import com.sds.nexledger.smartPass.controller.dto.offchain.DataRequestDTO;
import com.sds.nexledger.smartPass.controller.dto.offchain.OwnershipDTO;
import com.sds.nexledger.smartPass.controller.dto.offchain.OwnershipRequestDTO;
import com.sds.nexledger.smartPass.util.gson.DateDeserializer;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import io.restassured.path.json.config.JsonPathConfig;
import io.restassured.response.Response;
import lombok.extern.slf4j.Slf4j;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.jupiter.api.Assertions;
import org.junit.runner.RunWith;
import org.junit.runners.MethodSorters;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.restdocs.payload.JsonFieldType;
import org.springframework.restdocs.snippet.Attributes;
import org.springframework.test.context.junit4.SpringRunner;

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.IntStream;

import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.equalTo;
import static org.springframework.restdocs.payload.PayloadDocumentation.*;
import static org.springframework.restdocs.request.RequestDocumentation.*;
import static org.springframework.restdocs.restassured3.RestAssuredRestDocumentation.document;

@AutoConfigureRestDocs
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Slf4j
public class OffchainControllerRestAssuredTests extends CommonControllerTests {
    private final String BASE_URL = "/offchain/v1";
    @Autowired
    private Gson gson;
    private final JsonPathConfig jsonPahConfig = JsonPathConfig.jsonPathConfig()
            .gsonObjectMapperFactory((type, s) -> this.gson.newBuilder().create());

    @Test
    public void getDataAll() {
        try {
            String restDocumentName = "getDataAll";
            String url = BASE_URL+ "/data/all/metadata";

            Response response =
                    given(this.spec)
                        .accept(ContentType.JSON)
                        .filter(document(
                            restDocumentName,
                            ApiDocumentUtils.getDocumentRequestPreprocessor(),
                            ApiDocumentUtils.getDocumentResponsePreprocessor(),
//                            pathParameters(),
//                            requestFields(),
                            responseFields(
                                    fieldWithPath("[].dataId").type(JsonFieldType.STRING).description("dataId"),
                                    fieldWithPath("[].name").type(JsonFieldType.STRING).description("name"),
                                    fieldWithPath("[].type").type(JsonFieldType.STRING).description("type"),
                                    fieldWithPath("[].fileId").type(JsonFieldType.STRING).description("fileId"),
                                    fieldWithPath("[].description").type(JsonFieldType.STRING).description("description").optional(),
                                    fieldWithPath("[].hash").type(JsonFieldType.STRING).description("hash"),
                                    fieldWithPath("[].creator").type(JsonFieldType.STRING).description("creator"),
                                    fieldWithPath("[].updater").type(JsonFieldType.STRING).description("updater"),
                                    fieldWithPath("[].timestamp").type(JsonFieldType.STRING).attributes(Attributes.key("format").value(DateDeserializer.DEFAULT_DATE_FORMAT)).description("timestamp"),
                                    fieldWithPath("[].properties").type(JsonFieldType.OBJECT).description("properties").optional(),
                                    fieldWithPath("[].version").type(JsonFieldType.NUMBER).description("version"),
                                    fieldWithPath("[].status").type(JsonFieldType.STRING).description("status"),
                                    fieldWithPath("[].expirationTime").type(JsonFieldType.NUMBER).description("expirationTime").optional()
                            ))
                    )
                    .when()
                        .log().all()
                        .port(RestAssured.port)
                            .get(url)
                    .then()
                        .assertThat()
                            .statusCode(HttpStatus.OK.value())
                            .contentType(ContentType.JSON)
                            .log().all()
                            .extract().response();

            // ResponseBody JSON type Check
            String responseString = response.getBody().asString();
            List<DataDTO> list = gson.fromJson(responseString, TypeToken.getParameterized(List.class, DataDTO.class).getType());
            Assertions.assertNotNull(list);
        } catch (Exception e) {
            Assertions.fail(e);
        }
    }
}


REST-API Version (@Version)

https://code.sdsdev.co.kr/blockchain


RESTful의 HATEOAS

[https://pjh3749.tistory.com/260](https://pjh3749.tistory.com/260


TODO


AOP in Spring

Details

import io.micrometer.core.instrument.util.StringUtils;
import io.swagger.annotations.ApiOperation;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.support.AopUtils;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

// TODO : [LEGO-4663] - 옵션 제공 추가
@Profile({"default", "local", "dev"})
@Aspect
@Component
public class LegoControllerLoggingAOP {

    @Pointcut("within(@org.springframework.stereotype.Controller *) || within(@org.springframework.web.bind.annotation.RestController *)")
    public void pointcutController() {}

    @Pointcut("execution(* com.sds.lego..rest..*(..))")
    public void pointcutLegoRestPackage() {}


    @Around("pointcutController() && pointcutLegoRestPackage()")
    public Object aroundLegoRestController(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {

        HttpServletRequest request = findCurrentHttpServletRequest();
        Class<?> targetClass = AopUtils.getTargetClass(proceedingJoinPoint.getTarget());
        Method targetMethod = findTargetMethod(proceedingJoinPoint);

        if (ObjectUtils.allNotNull(request, targetClass, targetMethod)) {
            loggingRequestIfPossible(request, targetClass, targetMethod, proceedingJoinPoint.getArgs());
        }

        return proceedingJoinPoint.proceed();
    }


    private void loggingRequestIfPossible(HttpServletRequest request, Class<?> targetClass, Method targetMethod, Object[] args) {

        Logger logger = LoggerFactory.getLogger(targetClass);
        if (!logger.isDebugEnabled()) {
            return;
        }

        // ex) [GET] /rest/v2/blah/blah... (methodName) : methodDescription
        // ex) [GET] /rest/v2/blah/blah... (methodName)
        logger.debug("[{}] {} ({}){}",
            request.getMethod(),
            request.getRequestURI(),
            targetMethod.getName(),
            getApiOperationDescription(targetMethod));

        if (args != null && args.length > 0) {

            List<String> params = new ArrayList<>();
            for (Object arg : args) {
                if (arg == null) {
                    params.add("null");
                }
                else if (ClassUtils.isPrimitiveOrWrapper(arg.getClass())) {
                    params.add(arg.toString());
                }
                else {
                    params.add("{" + arg.getClass().getSimpleName() + "}");
                }
            }

            logger.debug("Parameters : {}", params);
        }
    }


    // region [Privates - Etc]

    private Method findTargetMethod(ProceedingJoinPoint proceedingJoinPoint) {

        Signature signature = proceedingJoinPoint.getSignature();
        if (signature instanceof MethodSignature) {
            return ((MethodSignature) signature).getMethod();
        }

        return null;
    }

    private HttpServletRequest findCurrentHttpServletRequest() {

        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        if (requestAttributes instanceof ServletRequestAttributes) {
            return ((ServletRequestAttributes) requestAttributes).getRequest();
        }

        return null;
    }

    private String getApiOperationDescription(Method targetMethod) {

        ApiOperation apiOperation = findAnnotation(targetMethod, ApiOperation.class);
        if (apiOperation == null) {
            return "";
        }

        String value = apiOperation.value();
        if (StringUtils.isBlank(value)) {
            return "";
        }

        return " : " + value;
    }

    @SuppressWarnings({"unchecked", "SameParameterValue"})
    private <T extends Annotation> T findAnnotation(Method targetMethod, Class<T> clazz) {

        Annotation[] annotations = targetMethod.getAnnotations();
        for (Annotation annotation : annotations) {
            if (clazz.isAssignableFrom(annotation.getClass())) {
                return (T) annotation;
            }
        }

        return null;
    }

    // endregion

}


Swagger (Api Documentation)

https://ksshlee.github.io/spring/springboot/swagger/


BeansUtil

https://velog.io/@kdhyo/BeanUtils.copyProperties%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EC%9E%90


Mockito

https://jdm.kr/blog/222


Swagger

https://www.baeldung.com/swagger-2-documentation-for-spring-rest-api

https://velog.io/@gillog/Swagger-UI-Annotation-%EC%84%A4%EB%AA%85


@Valid

https://bamdule.tistory.com/35

Details

Annotation Description
@NotNull Null 불가
@Null Null만 입력 가능
@NotEmpty Null, 빈 문자열 불가
@NotBlank Null, 빈 문자열, 스페이스만 있는 문자열 불가
@Size(min=,max=) 문자열, 배열 등의 크기가 만족하는가?
@Pattern(regex=) Regular Expression을 만족하는가?
@Max(숫자) 지정 값 이하인가?
@Min(숫자) 지정 값 이상인가?
@Positive 양수만 가능
@PositiveOrZero 양수와 0만 가능
@Negative 음수만 가능
@NegativeOrZero 음수와 0만 가능
@Digits(integer=,fraction=) 대상 수가 지정된 정수와 소수 자리 수 보다 작은가?
@DecimalMax(value=) 지정된 값(실수) 이하인가?
@DecimalMin(value=) 지정된 값(실수) 이상인가?
@Email 이메일 형식만 가능
@Future 현재보다 미래인가?
@Past 현재보다 과거인가?
@AssertFalse false인가?
@AssertTrue true인가?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment