개발일기/Spring + Unix Domain Socket

[Spring + UDS] 6. Spring property 분리

ignuy 2024. 8. 8.

코드는 OCP 원칙에 따라 변경에 닫혀있고 확장에 열려있어야 한다. 따라서 코드상에서 선언했던 SOCKET_PATH와 BUFFER_SIZE를 application.yml에서 설정하고 코드는 이를 받아 변수에 주입하도록 구현을 바꾸었다.

전체 코드

SocketProvider

package com.example.test;

import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j;
import org.newsclub.net.unix.AFSocketAddress;
import org.newsclub.net.unix.AFUNIXSocket;
import org.newsclub.net.unix.AFUNIXSocketAddress;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.URI;

@Slf4j
@Component
public class SocketProvider {

    @Value("${socket-provider.socket-path}")
    private String SOCKET_PATH;

    @Value("${socket-provider.buffer-size}")
    private int BUFFER_SIZE;
    private AFUNIXSocket sock;
    private OutputStream out;
    private InputStream in;

    @PostConstruct
    public void connect() throws IOException {
        SocketAddress endpoint = getSocketAddress(SOCKET_PATH);
        sock = AFUNIXSocket.connectTo(AFUNIXSocketAddress.of(endpoint));
        out = sock.getOutputStream();
        in = sock.getInputStream();

        log.info("UDS Connected Completely to {}", endpoint);
    }

    @PreDestroy
    public void disconnect() throws IOException {
        if (sock != null && !sock.isClosed()) {
            sock.close();
            log.info("Disconnected from server on " + SOCKET_PATH);
        }
    }

    // Need for custom return value for error handling
    public boolean isValid() {
        if (sock == null) {
            return false;
        } else if (!sock.isConnected()) {
            return false;
        }

        return true;
    }

    public void reconnect() throws IOException {
        if (this.isValid()) {
            return;
        }

        SocketAddress endpoint = getSocketAddress(SOCKET_PATH);
        sock = AFUNIXSocket.connectTo(AFUNIXSocketAddress.of(endpoint));
        out = sock.getOutputStream();
        in = sock.getInputStream();
    }

    public String sendMessage(String message) throws IOException {
        if (sock == null || !sock.isConnected()) {
            log.info("UDS NOT CONNECTED!!!");
            throw new IOException("UDS Not Connected");
        }
        log.info("Now writing string({}) to the server...", message);
        out.write(message.getBytes());
        out.flush();

        byte[] buffer = new byte[BUFFER_SIZE];
        int numRead = in.read(buffer);
        if (numRead > 0) {
            return new String(buffer, 0, numRead);
        } else {
            return null;
        }
    }

    private SocketAddress getSocketAddress(String socketName) throws IOException {
        if (socketName.startsWith("file:")) {
            // demo only: assume file: URLs are always handled by AFUNIXSocketAddress
            return AFUNIXSocketAddress.of(URI.create(socketName));
        } else if (socketName.contains(":/")) {
            // assume URI, e.g., unix:// or tipc://
            return AFSocketAddress.of(URI.create(socketName));
        }

        int colon = socketName.lastIndexOf(':');
        int slashOrBackslash = Math.max(socketName.lastIndexOf('/'), socketName.lastIndexOf('\\\\'));

        if (socketName.startsWith("@")) {
            // abstract namespace (Linux only!)
            return AFUNIXSocketAddress.inAbstractNamespace(socketName.substring(1));
        } else if (colon > 0 && slashOrBackslash < colon && !socketName.startsWith("/")) {
            // assume TCP socket
            String hostname = socketName.substring(0, colon);
            int port = Integer.parseInt(socketName.substring(colon + 1));
            return new InetSocketAddress(hostname, port);
        } else {
            // assume unix socket file name
            return AFUNIXSocketAddress.of(new File(socketName));
        }
    }
}

applicaiton.yml

spring:
  application:
    name: test

  profiles:
    active: dev

logging:
  level:
    com.example.test: info

socket-provider:
  socket-path: /tmp/my_unix_socket.sock
  buffer-size: 256

@Value

@Value 애너테이션은 생성자 주입 시 자동으로 주입되지 않는 기본 자료형과 문자열의 값을 설정한다.

application.yml에 있는 값을 가져올 일이 많지 않아 이를 관리할 클래스까지 만들 필요가 없을 때, 애너테이션 하나로 해결이 가능하다.

댓글