728x90
  • repeat(int count) 메서드는 원래 문자열을 지정된 횟수만큼 반복하여 새로운 문자열을 생성.
public class StringRepeatExample {
    public static void main(String[] args) {
        String str = "abc";
        String repeatedStr = str.repeat(3);
        System.out.println(repeatedStr);
    }
}

// 출력: abcabcabc
  • isBlank() 메서드는 문자열이 공백 문자열(길이가 0이거나 공백 문자로만 이루어진)인지 확인.
public class StringIsBlankExample {
    public static void main(String[] args) {
        String str1 = "";
        String str2 = " ";
        String str3 = "  \t  ";

        System.out.println(str1.isBlank());
        System.out.println(str2.isBlank());
        System.out.println(str3.isBlank());
    }
}
/** 출력
true
true
true
*/
  • lines() 메서드는 문자열을 줄바꿈으로 구분하여 스트림으로 반환.
import java.util.stream.Stream;

public class StringLinesExample {
    public static void main(String[] args) {
        String str = "Hello\nWorld\nJava";
        Stream<String> lines = str.lines();
        lines.forEach(System.out::println);
    }
}

/** 출력
Hello
World
Java
*/
  • strip() 메서드는 문자열의 앞뒤 공백을 제거한 새로운 문자열을 반환.
public class StringStripExample {
    public static void main(String[] args) {
        String str1 = "  abc   ";
        String str2 = "\t def \n";
        System.out.println(str1.strip());
        System.out.println(str2.strip());
    }
}
/** 출력
abc
def
*/
  • stripLeading() 메서드는 문자열의 앞쪽 공백을 제거한 새로운 문자열을 반환.
public class StringStripLeadingExample {
    public static void main(String[] args) {
        String str1 = "  abc   ";
        String str2 = "\t def \n";
        System.out.println(str1.stripLeading());
        System.out.println(str2.stripLeading());
    }
}
/**
abc
def
*/
  • stripTrailing() 메서드는 문자열의 뒤쪽 공백을 제거한 새로운 문자열을 반환.
public class StringStripTrailingExample {
    public static void main(String[] args) {
        String str1 = "  abc   ";
        String str2 = "\t def \n";
        System.out.println(str1.stripTrailing());
        System.out.println(str2.stripTrailing());
    }
}
/** 출력
abc
def
*/
  • formatted(Object... args) 메서드는 지정된 인자를 사용하여 문자열을 형식화하고 형식화된 문자열을 반환.
public class StringFormattedExample {
    public static void main(String[] args) {
        String str = "My name is %s, I'm %d years old.";
        String formattedStr = str.formatted( "John", 25);
        System.out.println(formattedStr);
    }
}
  • translateEscapes() 메서드는 자바 이스케이프 시퀀스를 해당 문자로 변환하여 새로운 문자열을 반환
public class StringTranslateEscapesExample {
    public static void main(String[] args) {
        String str = "Hello\\nWorld\\tJava";
        String translatedStr = str.translateEscapes();
        System.out.println(translatedStr);
    }
}
  • transform() 메서드는 문자열을 다른 인코딩 형식으로 변환.
public class StringTransformExample {
    public static void main(String[] args) {
        String str = "hello world";
        byte[] bytes = str.getBytes(StandardCharsets.UTF_8);
        String newStr = new String(bytes, StandardCharsets.ISO_8859_1);
        System.out.println(newStr);
    }
}

 

이상끝!

728x90

Spring Boot에서는 전역적으로 적용되는 날짜 및 시간 형식을 지정할 수 있습니다. 이를 통해 코드에서 중복되는 날짜 및 시간 형식 코드를 제거하고 코드의 가독성과 유지 보수성을 향상시킬 수 있습니다.

여러가지 방안이 있겠습니다만 저는 아래같은 방식으로 진행해왔으니 참고하시면 될것 같습니다.

@JsonFormat 어노테이션

@Data
public class MyDateTime {
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", timezone = "UTC")

    private Date dateTime;
}

WebMvcConfigurer 인터페이스 구현

public class WebMvcConfig implements WebMvcConfigurer {
    @Bean
    public Jackson2ObjectMapperBuilder jacksonBuilder() {
        Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
        builder.timeZone(TimeZone.getDefault());
        return builder;
    }
}

postman 으로 테스트 할 controller 를 하나 만들어보겠습니다.

@RestController
@RequestMapping("/")
public class dataController {
    @GetMapping("/dateTime")
    public MyDateTime getMyDateTime() {
        MyDateTime myDateTime = new MyDateTime();
        myDateTime.setDateTime(new Date());
        return myDateTime;
    }
}

@JsonComponent 어노테이션 사용하는 방법

CustomDateSerializer 를 작성합니다.

public class CustomDateSerializer extends JsonSerializer<Date> {
    private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");

    @Override
    public void serialize(Date value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
        jgen.writeString(dateFormat.format(value));
    }
}

CustomDateDeserializer를 작성 합니다.

public class CustomDateDeserializer extends JsonDeserializer<Date> {
    private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");

    @Override
    public Date deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
        JsonNode node = jp.getCodec().readTree(jp);
        String dateString = node.textValue();
        try {
            return dateFormat.parse(dateString);
        } catch (ParseException e) {
            throw new IOException("Invalid date format: " + dateString, e);
        }
    }
}

새로운 응답형식으로 객체 하나 더 만들어 볼게요.

@Data
public class MyResponse {
    private String message;
    private Date timestamp;
}

마지막으로 postman 이 호출할 controller 를 하나 추가로 만들어 볼게요.

public class MyController {
    @GetMapping("/myEndpoint")
    public MyResponse getMyResponse() {
        MyResponse response = new MyResponse();
        response.setMessage("안녕! 난 블레이크야!");
        response.setTimestamp(new Date());
        return response;
    }
}

이상으로 어노테이션 하나로 전역시간에 대한 처리하는 방법을 대략적으로 알아봤습니다.


내저장소 바로가기 luxury515

728x90

RequestBodyAdvice의 사용법에 대해 묻는 사람이 있어 이에 대해 글을 쓰게 되었습니다.

암호화 및 복호화 자체는 어렵지 않지만, 언제 처리해야 할까요? 필터를 정의하여 요청과 응답을 각각 가로채 처리하는 것도 가능하지만 이 방법은 거칠지만 유연합니다. 왜냐하면, 첫 번째 손에 요청 매개 변수와 응답 데이터를 얻을 수 있기 때문입니다. 그러나 SpringMVC에서는 ResponseBodyAdvice와 RequestBodyAdvice를 제공하여 요청과 응답을 사전 처리할 수 있도록 지원합니다.

그래서 오늘 이 글은 두 가지 목적을 가지고 있습니다:

매개 변수/응답 암호화 및 복호화에 대한 아이디어 공유. ResponseBodyAdvice와 RequestBodyAdvice의 사용법을 공유합니다. 그러면 이제 본론으로 들어가 보겠습니다.

1.개발 암호화 starter 우리가 개발하는 이 도구를 보다 일반화하기 위해, 그리고 스프링 부트 스타터를 직접 정의하기 위해 이 도구를 starter로 만들겠습니다. 이후에는 Spring Boot 프로젝트에서 바로 사용할 수 있습니다.

먼저 Spring Boot 프로젝트를 생성하고, spring-boot-starter-web 의존성을 추가합니다.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <scope>provided</scope>
    <version>2.4.3</version>
</dependency>

의존성을 추가한 후에는 미리 사용할 수 있도록 암호화 유틸리티 클래스를 정의합니다. 암호화 방법에는 대칭 키 암호화와 비대칭 키 암호화가 있으며, 대칭 키 암호화에서는 AES, DES, 3DES 등 다양한 알고리즘이 사용됩니다. 여기에서는 Java에서 제공하는 Cipher를 사용하여 대칭 키 암호화 방식 중 AES 알고리즘을 사용합니다.

public class AESUtils {

    private static final String AES_ALGORITHM = "AES/ECB/PKCS5Padding";

    // cipher 얻기
    private static Cipher getCipher(byte[] key, int model) throws Exception {
        SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
        Cipher cipher = Cipher.getInstance(AES_ALGORITHM);
        cipher.init(model, secretKeySpec);
        return cipher;
    }

    // AES 암호화
    public static String encrypt(byte[] data, byte[] key) throws Exception {
        Cipher cipher = getCipher(key, Cipher.ENCRYPT_MODE);
        return Base64.getEncoder().encodeToString(cipher.doFinal(data));
    }

    // AES 복호화
    public static byte[] decrypt(byte[] data, byte[] key) throws Exception {
        Cipher cipher = getCipher(key, Cipher.DECRYPT_MODE);
        return cipher.doFinal(Base64.getDecoder().decode(data));
    }
}

이 Utils 클래스는 간단하므로 따로 설명은 필요하지 않을것 같습니다. 그러나 암호화된 데이터는 가독성이 없을 수 있으므로 일반적으로 암호화된 데이터를 Base64 알고리즘을 사용하여 인코딩하여 읽을 수 있는 문자열을 얻을 수 있습니다. 다시 말해서, 위의 AES 암호화 메소드의 반환 값은 Base64로 인코딩 된 문자열입니다. AES 복호화 메소드의 매개 변수도 Base64로 인코딩 된 문자열이며, 이 문자열을 먼저 디코딩 한 다음 해독해야합니다.

그 다음 우리는 예비로 응답 도구 클래스를 패키징 할 것입니다. 이것은 사람들이 종종 송건 비디오를보고 이미 잘 이해할 것입니다.

public class RespBean {
    private Integer status;
    private String msg;
    private Object obj;

    public static RespBean build() {
        return new RespBean();
    }

    public static RespBean ok(String msg) {
        return new RespBean(200, msg, null);
    }

    public static RespBean ok(String msg, Object obj) {
        return new RespBean(200, msg, obj);
    }

    public static RespBean error(String msg) {
        return new RespBean(500, msg, null);
    }

    public static RespBean error(String msg, Object obj) {
        return new RespBean(500, msg, obj);
    }

    private RespBean() {
    }

    private RespBean(Integer status, String msg, Object obj) {
        this.status = status;
        this.msg = msg;
        this.obj = obj;
    }

    public Integer getStatus() {
        return status;
    }

    public RespBean setStatus(Integer status) {
        this.status = status;
        return this;
    }

    public String getMsg() {
        return msg;
    }

    public RespBean setMsg(String msg) {
        this.msg = msg;
        return this;
    }

    public Object getObj() {
        return obj;
    }

    public RespBean setObj(Object obj) {
        this.obj = obj;
        return this;
    }
}

@Decrypt 와 @Encrypt 를 만들어 볼게요.

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.PARAMETER})
public @interface Decrypt {
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Encrypt {
}

혹은 사용자가 직접 암호화 키를 구성할 수 있기 때문에 EncryptProperties 클래스를 정의하여 사용자가 구성한 키를 읽도록 합니다.

@ConfigurationProperties(prefix = "spring.encrypt")
public class EncryptProperties {
    private final static String DEFAULT_KEY = "https://rainsister.tistory.com/";
    private String key = DEFAULT_KEY;

    public String getKey() {
        return key;
    }

    public void setKey(String key) {
        this.key = key;
    }
}

이후 key는 application.properties (혹은 application.yml) 파일에 선언해 놓으면 됩니다.

spring.encrypt.key=xxx // 이런 식으로 

암호화

@EnableConfigurationProperties(EncryptProperties.class)
@ControllerAdvice
public class EncryptResponse implements ResponseBodyAdvice<RespBean> {
    private ObjectMapper om = new ObjectMapper();
    @Autowired
    EncryptProperties encryptProperties;
    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        return returnType.hasMethodAnnotation(Encrypt.class);
    }

    @Override
    public RespBean beforeBodyWrite(RespBean body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        byte[] keyBytes = encryptProperties.getKey().getBytes();
        try {
            if (body.getMsg()!=null) {
                body.setMsg(AESUtils.encrypt(body.getMsg().getBytes(),keyBytes));
            }
            if (body.getObj() != null) {
                body.setObj(AESUtils.encrypt(om.writeValueAsBytes(body.getObj()), keyBytes));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return body;
    }
}

supports: 이 메서드는 어떤 유형의 인터페이스가 암호화되어야 하는지를 판단하기 위해 사용됩니다. returnType 매개 변수는 반환 유형을 나타내며, 여기에서 판단 로직은 해당 인터페이스에 @Encrypt 주석이 포함되어 있는지 여부입니다. 있으면 해당 인터페이스는 암호화 처리가 필요하며, 없으면 해당 인터페이스는 암호화 처리가 필요하지 않습니다.

beforeBodyWrite: 이 메서드는 데이터 응답 전에 실행됩니다. 즉, 우리는 응답 데이터를 먼저 두 번째 처리하고 처리가 완료되면 json으로 반환합니다. 여기에서는 처리 방법이 매우 간단합니다. RespBean의 status는 상태 코드이므로 암호화하지 않아도됩니다. 두 개의 다른 필드를 다시 암호화하고 다시 값을 설정하면됩니다.

주의!사용자 정의 ResponseBodyAdvice는 @ControllerAdvice 주석을 사용하여 표시해야합니다.

복호화

@EnableConfigurationProperties(EncryptProperties.class)
@ControllerAdvice
public class DecryptRequest extends RequestBodyAdviceAdapter {
    @Autowired
    EncryptProperties encryptProperties;
    @Override
    public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        return methodParameter.hasMethodAnnotation(Decrypt.class) || methodParameter.hasParameterAnnotation(Decrypt.class);
    }

    @Override
    public HttpInputMessage beforeBodyRead(final HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
        byte[] body = new byte[inputMessage.getBody().available()];
        inputMessage.getBody().read(body);
        try {
            byte[] decrypt = AESUtils.decrypt(body, encryptProperties.getKey().getBytes());
            final ByteArrayInputStream bais = new ByteArrayInputStream(decrypt);
            return new HttpInputMessage() {
                @Override
                public InputStream getBody() throws IOException {
                    return bais;
                }

                @Override
                public HttpHeaders getHeaders() {
                    return inputMessage.getHeaders();
                }
            };
        } catch (Exception e) {
            e.printStackTrace();
        }
        return super.beforeBodyRead(inputMessage, parameter, targetType, converterType);
    }
}

DecryptRequest 클래스에서는 RequestBodyAdvice 인터페이스를 직접 구현하는 대신 RequestBodyAdviceAdapter 클래스를 상속합니다. RequestBodyAdviceAdapter 클래스는 RequestBodyAdvice 인터페이스의 하위 클래스이며, 인터페이스의 일부 메서드를 구현하므로 RequestBodyAdviceAdapter를 상속하면 실제 요구 사항에 따라 일부 메서드만 구현하면됩니다.

supports: 이 메서드는 어떤 인터페이스가 인터페이스 복호화를 처리해야 하는지를 판단하기 위해 사용됩니다. 여기에서 판단 로직은 @Decrypt 주석이 메서드 또는 매개 변수에 포함되어 있는지 여부입니다. 복호화 문제를 처리합니다.

beforeBodyRead: 이 메서드는 매개 변수가 구체적인 객체로 변환되기 전에 실행됩니다. 먼저 데이터를 스트림에서 로드한 다음 데이터를 복호화하고 복호화가 완료되면 HttpInputMessage 객체를 다시 구성하여 반환합니다.

사용은 어떻게?

public class User {
    private Long id;
    private String username;
    //생략 getter/setter
}
@RestController
public class HelloController {
    @GetMapping("/user")
    @Encrypt
    public RespBean getUser() {
        User user = new User();
        user.setId((long) 99);
        user.setUsername("javaboy");
        return RespBean.ok("ok", user);
    }

    @PostMapping("/user")
    public RespBean addUser(@RequestBody @Decrypt User user) {
        System.out.println("user = " + user);
        return RespBean.ok("ok", user);
    }
}
spring.encrypt.key=1234567890123456


내저장소 바로가기 luxury515

728x90

ERROR CASE01:

java.lang.NullPo0interException ==> mybatis config 파일 ==> sql xml 경로 틀려서 생기는 에러

RESOLVE:

ERROR CASE02:

mybatis config 파일의 sql.xml 경러 지정해 줘야 함.

RESOLVE:

ERROR CASE03:

JSP 파일 상단의 CLASS_NAME PATH 설정일 잘 못됨.(FOLD NAME.JAVA FILE NAME) RESOLVE:


내저장소 바로가기 luxury515

+ Recent posts