반응형

 

실제 안드로이드 기기 없이 Mac의 에뮬레이터 환경에서 카카오톡 채팅봇을 구축하는 방법을 단계별로 설명합니다.


목차

  1. 개요 및 동작 원리
  2. 사전 준비물
  3. BlueStacks 설치
  4. 카카오톡 설치 및 계정 설정
  5. 메신저봇R 설치 및 권한 설정
  6. 봇 스크립트 작성
  7. 테스트 및 디버깅
  8. 운영 시 주의사항
  9. 마무리

1. 개요 및 동작 원리

메신저봇R이란?

메신저봇R은 안드로이드의 알림(Notification) 시스템을 이용해 카카오톡 메시지를 감지하고, 미리 작성해둔 JavaScript 스크립트에 따라 자동으로 응답하는 앱입니다.

동작 흐름

카카오톡 메시지 수신
        ↓
 안드로이드 알림 발생
        ↓
 메신저봇R이 알림 감지
        ↓
 JavaScript 스크립트 실행 (response 함수)
        ↓
 카카오톡 알림 액션으로 자동 답장

왜 BlueStacks인가?

안드로이드 스튜디오의 AVD(Android Virtual Device)보다 BlueStacks를 권장하는 이유는 다음과 같습니다.

  • Google Play Services가 기본 탑재되어 앱 호환성이 높음
  • 카카오톡 에뮬레이터 감지 우회 가능성이 상대적으로 높음
  • Mac용 네이티브 빌드 지원 (Intel / Apple Silicon 모두 지원)
  • 설정이 간단하고 UI가 직관적

2. 사전 준비물

시작하기 전에 아래 항목들을 준비해주세요.

항목 설명
Mac (Intel 또는 Apple Silicon) macOS 11 Big Sur 이상 권장
여유 저장 공간 최소 10GB 이상
여분의 전화번호 카카오톡 봇 전용 계정 인증용
카카오 계정 (신규) 봇 전용으로 새로 만들 것을 권장
메신저봇R APK Play Store 또는 공식 채널에서 다운로드

⚠️ 중요: 기존에 사용 중인 카카오톡 계정을 그대로 사용하면 기존 기기와 충돌할 수 있습니다. 봇 전용 신규 계정을 만들고, 봇을 운영할 오픈채팅방에 해당 계정을 참여시키는 방식을 권장합니다.


3. BlueStacks 설치

3-1. 자신의 Mac 환경 확인

터미널에서 아래 명령어로 CPU 종류를 확인합니다.

uname -m
# x86_64 → Intel Mac
# arm64  → Apple Silicon (M1/M2/M3)

3-2. BlueStacks 다운로드

BlueStacks 공식 홈페이지에 접속해 Mac용 최신 버전을 다운로드합니다.

  • Intel Mac: BlueStacks 5 (x64)
  • Apple Silicon: BlueStacks 5 (Silicon) — 별도 빌드 선택

💡 Apple Silicon의 경우 공식 홈페이지에서 "Apple Silicon" 버전을 명시적으로 선택해야 합니다.

3-3. 설치 및 초기 설정

  1. 다운로드한 .dmg 파일을 실행합니다.
  2. 설치 마법사를 따라 진행하면 BlueStacks가 설치됩니다.
  3. 최초 실행 시 Google 계정 로그인을 요구합니다. 봇 전용 Google 계정을 사용하거나 기존 계정으로 로그인합니다.
  4. 로그인 후 Play Store가 정상적으로 활성화되는지 확인합니다.

3-4. 권장 에뮬레이터 설정

BlueStacks 우측 사이드바의 설정(⚙️)환경 설정에서 아래와 같이 설정합니다.

설정 항목 권장값
CPU 코어 수 2 이상
RAM 2048MB 이상
해상도 1280 x 720
Android 버전 Android 9 이상

4. 카카오톡 설치 및 계정 설정

4-1. 카카오톡 설치

  1. BlueStacks 내부의 Google Play Store를 실행합니다.
  2. 검색창에 카카오톡을 입력하고 설치합니다.
  3. 설치가 완료되면 카카오톡을 실행합니다.

⚠️ 일부 버전에서는 에뮬레이터 환경을 감지해 실행을 거부할 수 있습니다. 이 경우 BlueStacks 설정에서 루트 권한 비활성화를 확인하고, Android 버전을 변경해 재시도해보세요.

4-2. 봇 전용 계정으로 로그인

  1. 카카오톡 실행 후 새 계정 만들기 또는 기존 봇 계정으로 로그인합니다.
  2. 전화번호 인증 단계에서 준비해둔 여분 회선의 번호를 입력합니다.
  3. 해당 번호로 수신된 SMS 인증번호를 입력해 인증을 완료합니다.
  4. 로그인 완료 후 봇이 활동할 오픈채팅방에 참여합니다.

4-3. 알림 설정 확인

메신저봇R이 메시지를 감지하려면 카카오톡 알림이 반드시 활성화되어 있어야 합니다.

  • 카카오톡 앱 내 설정 → 알림 → 모든 알림 켜기
  • BlueStacks 설정 → 알림 → 알림 표시 활성화

5. 메신저봇R 설치 및 권한 설정

5-1. 메신저봇R 설치

메신저봇R은 Play Store에서 검색되지 않는 경우가 있습니다. 이 경우 APK 파일을 직접 설치합니다.

방법 1: Play Store에서 "메신저봇R" 검색 후 설치
방법 2: 공식 오픈채팅방이나 신뢰할 수 있는 출처에서 APK 다운로드 후 설치

APK를 직접 설치하려면 BlueStacks의 사이드바에서 APK 설치 버튼을 클릭하거나, APK 파일을 BlueStacks 화면으로 드래그 앤 드롭합니다.

5-2. 알림 접근 권한 허용 (가장 중요!)

메신저봇R이 정상 동작하려면 알림 읽기 권한이 반드시 필요합니다.

  1. 메신저봇R 앱을 처음 실행하면 권한 요청 팝업이 뜹니다.
  2. "알림 접근 허용" 버튼을 눌러 Android 시스템 설정으로 이동합니다.
  3. 메신저봇R 항목을 찾아 알림 접근 허용을 활성화합니다.

경로: Android 설정 → 앱 → 메신저봇R → 알림 접근

이 권한이 없으면 메시지를 아예 감지하지 못합니다. 권한 설정 후 앱을 재시작하세요.

5-3. 봇 활성화

  1. 메신저봇R 메인 화면에서 전원 버튼(▶)을 눌러 봇을 활성화합니다.
  2. 상단에 초록색 ON 상태가 표시되면 봇이 동작 중입니다.
  3. 이 상태에서 카카오톡으로 메시지를 받으면 스크립트가 실행됩니다.

6. 봇 스크립트 작성

메신저봇R은 JavaScript로 봇 로직을 작성합니다. 메인 함수는 response입니다.

6-1. response 함수 파라미터

function response(room, msg, sender, isGroupChat, replier, imageDB, packageName) {
    // room       : 채팅방 이름 (String)
    // msg        : 수신된 메시지 내용 (String)
    // sender     : 보낸 사람 닉네임 (String)
    // isGroupChat: 단체 채팅방 여부 (Boolean)
    // replier    : 답장 객체 (replier.reply()로 답장)
    // imageDB    : 이미지 관련 객체
    // packageName: 메시지를 보낸 앱의 패키지명 (String)
}

6-2. 기본 예제 - 특정 채팅방에서만 응답

function response(room, msg, sender, isGroupChat, replier, imageDB, packageName) {

    // 카카오톡 메시지만 처리
    if (packageName !== "com.kakao.talk") return;

    // 특정 오픈채팅방에서만 동작
    if (room !== "내 오픈채팅방 이름") return;

    // 인사 응답
    if (msg === "안녕" || msg === "안녕하세요") {
        replier.reply("안녕하세요! 무엇을 도와드릴까요? 😊");
        return;
    }

    // 도움말 명령어
    if (msg === "!도움말") {
        replier.reply(
            "📋 사용 가능한 명령어\n" +
            "!도움말 - 명령어 목록\n" +
            "!정보 - 봇 정보\n" +
            "!시간 - 현재 시간"
        );
        return;
    }

    // 현재 시간 출력
    if (msg === "!시간") {
        var now = new Date();
        replier.reply("현재 시간: " + now.toLocaleString("ko-KR"));
        return;
    }

    // 봇 정보
    if (msg === "!정보") {
        replier.reply("🤖 카카오톡 자동응답 봇 v1.0\nBlueStacks + 메신저봇R로 동작 중입니다.");
        return;
    }
}

6-3. 심화 예제 - 키워드 감지 및 조건 분기

function response(room, msg, sender, isGroupChat, replier, imageDB, packageName) {

    if (packageName !== "com.kakao.talk") return;
    if (room !== "내 오픈채팅방 이름") return;

    var lowerMsg = msg.toLowerCase();

    // 욕설 감지 및 경고 (예시)
    var badWords = ["욕설1", "욕설2"];
    for (var i = 0; i < badWords.length; i++) {
        if (msg.indexOf(badWords[i]) !== -1) {
            replier.reply("⚠️ " + sender + "님, 채팅 규칙을 지켜주세요!");
            return;
        }
    }

    // 특정 사람에게만 반응
    if (sender === "관리자닉네임" && msg.startsWith("!공지")) {
        var notice = msg.replace("!공지 ", "");
        replier.reply("📢 [공지사항]\n" + notice);
        return;
    }

    // 주사위 기능
    if (msg === "!주사위") {
        var result = Math.floor(Math.random() * 6) + 1;
        replier.reply(sender + "님의 주사위 결과: 🎲 " + result);
        return;
    }

    // 반복 메시지 에코 (테스트용)
    if (msg.startsWith("!에코 ")) {
        replier.reply(msg.replace("!에코 ", ""));
        return;
    }
}

6-4. 스크립트 적용 방법

  1. 메신저봇R 앱 하단의 스크립트 편집기 탭으로 이동합니다.
  2. 기존 코드를 모두 삭제하고 작성한 스크립트를 붙여넣습니다.
  3. 우측 상단의 저장(💾) 버튼을 누릅니다.
  4. 컴파일(▶) 버튼을 눌러 문법 오류가 없는지 확인합니다.
  5. 봇이 ON 상태인지 다시 확인합니다.

7. 테스트 및 디버깅

7-1. 기본 동작 테스트

  1. 다른 기기(혹은 다른 카카오톡 계정)에서 봇이 참여한 오픈채팅방에 메시지를 보냅니다.
  2. 봇 계정이 자동으로 답장하는지 확인합니다.
  3. !도움말 등 미리 정의한 명령어를 입력해 정상 동작 여부를 체크합니다.

7-2. 로그 확인

메신저봇R의 로그 탭에서 메시지 수신 및 응답 내역을 확인할 수 있습니다.

[INFO] room: 내 오픈채팅방, sender: 테스터, msg: !도움말
[INFO] reply sent: 📋 사용 가능한 명령어...

7-3. 자주 발생하는 문제

문제 원인 해결 방법
봇이 응답하지 않음 알림 접근 권한 미허용 설정에서 권한 재확인
봇이 응답하지 않음 카카오톡 알림 비활성화 카카오톡 알림 설정 켜기
일부 메시지만 감지됨 room 이름 불일치 로그에서 실제 room 이름 확인 후 수정
스크립트 오류 JavaScript 문법 오류 편집기에서 컴파일 후 오류 메시지 확인
카카오톡 설치 불가 에뮬레이터 감지 BlueStacks 버전 변경 또는 루트 비활성화

8. 운영 시 주의사항

8-1. Mac 절전 모드 해제

BlueStacks가 실행 중이더라도 Mac이 잠자기 모드에 들어가면 봇이 중단됩니다. 아래 설정으로 절전을 막아주세요.

방법 1: 시스템 설정에서 변경

시스템 설정 → 배터리(또는 에너지 절약) → 디스플레이 끄기 시간을 늘리거나 "절대 끄지 않음"으로 설정

방법 2: 터미널 명령어 (일시적)

# 카페인 앱처럼 절전 방지 (터미널 열어두는 동안 유지)
caffeinate -d

8-2. 카카오톡 계정 정책

  • 자동응답 봇은 카카오톡 이용약관상 회색 지대에 해당합니다.
  • 과도한 자동 메시지 발송 시 계정이 제한될 수 있습니다.
  • 봇 전용 계정을 사용해 메인 계정이 영향을 받지 않도록 합니다.

8-3. 리소스 관리

BlueStacks는 상당한 CPU와 메모리를 소모합니다. 장시간 운영 시:

  • 불필요한 BlueStacks 앱은 종료해 리소스를 확보하세요.
  • 주기적으로 BlueStacks를 재시작해 메모리 누수를 방지하세요.
  • Mac의 온도와 팬 소음을 주기적으로 확인하세요.

9. 마무리

이번 포스트에서는 Mac 환경에서 BlueStacks와 메신저봇R을 활용해 카카오톡 오픈채팅방 자동응답 봇을 구축하는 방법을 알아보았습니다.

요약

  1. BlueStacks 설치 (Mac 아키텍처에 맞는 버전 선택)
  2. 카카오톡 설치 → 봇 전용 계정으로 인증 및 로그인
  3. 메신저봇R 설치 → 알림 접근 권한 허용
  4. JavaScript 스크립트 작성 및 적용
  5. 봇 활성화 후 테스트

실제 기기 없이도 충분히 구축 가능하지만, 24시간 안정적인 운영이 필요하다면 중고 저가 안드로이드 폰을 별도 기기로 사용하는 것이 장기적으로 더 안정적입니다.


이 포스트가 도움이 되었다면 공유 부탁드립니다! 궁금한 점은 댓글로 남겨주세요. 🙌

반응형
반응형

 

 

웹 브라우저에서 3D 그래픽을 구현하는 것은 한때 Flash나 별도 플러그인의 영역이었습니다. 하지만 WebGL의 등장과 함께 브라우저 네이티브 3D 렌더링이 가능해졌고, Three.js는 이 WebGL을 가장 쉽고 강력하게 다룰 수 있는 JavaScript 라이브러리로 자리잡았습니다. 이 글에서는 Three.js의 핵심 아키텍처, 주요 개념, 그리고 실전 활용 패턴까지 정리합니다.

요약

  • Three.js란: WebGL을 추상화한 JavaScript 3D 라이브러리로, 복잡한 셰이더·행렬 연산 없이 선언적으로 3D 장면을 구성할 수 있음.
  • 핵심 구성요소: Scene, Camera, Renderer, Mesh(Geometry + Material), Light.
  • 활용 분야: 인터랙티브 웹사이트, 데이터 시각화, 게임, 제품 뷰어, AR/VR 등.

1. Three.js가 필요한 이유

WebGL의 한계

WebGL은 GPU 가속 2D/3D 렌더링을 위한 저수준 API입니다. 삼각형 하나를 그리기 위해서도 다음이 필요합니다:

  • 버텍스/프래그먼트 셰이더 작성 (GLSL)
  • 버퍼 생성 및 데이터 바인딩
  • 행렬 변환 (모델, 뷰, 프로젝션)
  • 렌더 루프 관리
// 순수 WebGL로 삼각형 하나 그리기 — 약 100줄 이상의 보일러플레이트
const gl = canvas.getContext('webgl');
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, `
  attribute vec4 aPosition;
  void main() { gl_Position = aPosition; }
`);
// ... 셰이더 컴파일, 링킹, 버퍼 설정 등 수십 줄 ...

Three.js의 추상화

Three.js는 이 모든 저수준 작업을 감추고, 직관적인 객체 모델을 제공합니다:

// Three.js로 동일한 결과를 얻기
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();

const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);

2. 핵심 아키텍처 — Scene Graph

Three.js의 근간은 Scene Graph(장면 그래프) 패턴입니다. 이는 3D 객체들을 트리 구조로 관리하는 디자인 패턴으로, 부모-자식 관계를 통해 변환(위치, 회전, 스케일)이 상속됩니다.

Scene (루트)
├── Group (자동차)
│   ├── Mesh (차체)
│   ├── Mesh (바퀴 1)
│   ├── Mesh (바퀴 2)
│   └── PointLight (헤드라이트)
├── DirectionalLight (태양)
└── Mesh (지면)

주요 구성요소

구성요소 역할 주요 클래스
Scene 모든 3D 객체를 담는 컨테이너 THREE.Scene
Camera 장면을 바라보는 시점 정의 PerspectiveCamera, OrthographicCamera
Renderer Scene + Camera → 2D 이미지 변환 WebGLRenderer
Geometry 꼭짓점, 면 등 형상 데이터 BoxGeometry, SphereGeometry, BufferGeometry
Material 표면의 색상, 질감, 반사 속성 MeshStandardMaterial, ShaderMaterial
Mesh Geometry + Material의 결합체 THREE.Mesh
Light 장면의 조명 DirectionalLight, PointLight, AmbientLight

3. 렌더링 파이프라인

Three.js의 렌더링은 매 프레임마다 다음 과정을 거칩니다:

Scene Graph 순회
    → 월드 행렬 계산 (위치·회전·스케일)
    → 카메라 시점 기준 절두체 컬링 (보이지 않는 객체 제외)
    → Material별 정렬 (불투명 → 투명 순)
    → WebGL Draw Call 실행
    → 프레임 버퍼 → 화면 출력

렌더 루프 구현

function animate() {
  requestAnimationFrame(animate);

  // 상태 업데이트
  cube.rotation.x += 0.01;
  cube.rotation.y += 0.01;

  // 렌더링
  renderer.render(scene, camera);
}
animate();

requestAnimationFrame을 사용하면 브라우저의 화면 갱신 주기(보통 60fps)에 맞춰 렌더링이 수행되며, 탭이 비활성화되면 자동으로 일시정지됩니다.

4. 카메라 시스템

PerspectiveCamera (원근 투영)

사람의 눈과 유사한 원근감을 제공합니다. 가까운 물체는 크게, 먼 물체는 작게 보입니다.

const camera = new THREE.PerspectiveCamera(
  75,                           // fov: 시야각 (도)
  window.innerWidth / window.innerHeight, // aspect: 종횡비
  0.1,                          // near: 가까운 클리핑 면
  1000                          // far: 먼 클리핑 면
);
camera.position.set(0, 5, 10);
camera.lookAt(0, 0, 0);

OrthographicCamera (직교 투영)

거리에 관계없이 동일한 크기로 렌더링됩니다. 2D 게임, CAD, 아이소메트릭 뷰에 적합합니다.

const camera = new THREE.OrthographicCamera(
  -10, 10,   // left, right
  10, -10,   // top, bottom
  0.1, 1000  // near, far
);

5. Material과 조명 시스템

PBR (Physically Based Rendering)

Three.js의 MeshStandardMaterial은 PBR 워크플로우를 지원하여 물리적으로 정확한 렌더링 결과를 제공합니다.

const material = new THREE.MeshStandardMaterial({
  color: 0x8B4513,       // 기본 색상
  metalness: 0.3,        // 금속성 (0: 비금속, 1: 금속)
  roughness: 0.7,        // 거칠기 (0: 매끄러움, 1: 거침)
  map: texture,          // 텍스처 맵
  normalMap: normalTex,  // 노멀 맵 (표면 디테일)
  envMap: envTexture,    // 환경 맵 (반사)
});

주요 조명 유형

// 환경광 — 장면 전체를 균일하게 밝힘
const ambient = new THREE.AmbientLight(0x404040, 0.5);

// 방향광 — 태양처럼 평행한 빛 (그림자 지원)
const directional = new THREE.DirectionalLight(0xffffff, 1);
directional.position.set(5, 10, 5);
directional.castShadow = true;

// 점광원 — 전구처럼 한 점에서 퍼지는 빛
const point = new THREE.PointLight(0xff6600, 1, 50);
point.position.set(0, 5, 0);

6. 지오메트리와 커스텀 메시

내장 지오메트리

Three.js는 다양한 기본 도형을 제공합니다:

new THREE.BoxGeometry(1, 1, 1);           // 정육면체
new THREE.SphereGeometry(1, 32, 32);      // 구
new THREE.CylinderGeometry(0.5, 0.5, 2);  // 원기둥
new THREE.PlaneGeometry(10, 10);          // 평면
new THREE.TorusKnotGeometry(1, 0.3, 100, 16); // 토러스 매듭

BufferGeometry로 커스텀 형상 만들기

성능을 위해 BufferGeometryFloat32Array를 직접 사용할 수 있습니다:

const geometry = new THREE.BufferGeometry();
const vertices = new Float32Array([
  -1, -1, 0,
   1, -1, 0,
   0,  1, 0,
]);
geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));

외부 3D 모델 로드

실무에서는 Blender 등에서 제작한 모델을 불러오는 경우가 대부분입니다:

import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';

const loader = new GLTFLoader();
loader.load('/models/robot.glb', (gltf) => {
  const model = gltf.scene;
  model.scale.set(0.5, 0.5, 0.5);
  scene.add(model);
});

glTF(.glb/.gltf)는 웹 3D의 사실상 표준 포맷으로, 메시·재질·애니메이션·스켈레톤을 하나의 파일에 담을 수 있습니다.

7. 성능 최적화 전략

3D 웹 애플리케이션에서 성능은 핵심 과제입니다. 주요 최적화 기법을 정리합니다.

Draw Call 최소화

GPU 성능 병목의 대부분은 Draw Call 수에서 발생합니다.

// 같은 지오메트리를 여러 개 배치할 때 — InstancedMesh 사용
const mesh = new THREE.InstancedMesh(geometry, material, 1000);
const dummy = new THREE.Object3D();

for (let i = 0; i < 1000; i++) {
  dummy.position.set(Math.random() * 100, 0, Math.random() * 100);
  dummy.updateMatrix();
  mesh.setMatrixAt(i, dummy.matrix);
}
scene.add(mesh); // 1000개 객체를 단 1회 Draw Call로 렌더링

LOD (Level of Detail)

카메라와의 거리에 따라 디테일 수준을 조절합니다:

const lod = new THREE.LOD();
lod.addLevel(highDetailMesh, 0);    // 가까울 때
lod.addLevel(mediumDetailMesh, 50); // 중간 거리
lod.addLevel(lowDetailMesh, 100);   // 멀 때
scene.add(lod);

기타 최적화 체크리스트

  • 텍스처: 2의 거듭제곱 크기 사용 (512×512, 1024×1024), 필요시 압축 텍스처(Basis/KTX2) 적용.
  • 지오메트리: 불필요한 폴리곤 제거, BufferGeometry 사용 (기본값).
  • 그림자: shadowMap 해상도 적절히 조절, 그림자가 불필요한 객체는 castShadow = false.
  • 렌더러 설정: renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)) 로 레티나 디스플레이 대응.
  • dispose: 사용 완료된 지오메트리·재질·텍스처는 반드시 dispose() 호출하여 GPU 메모리 해제.
// 메모리 누수 방지
geometry.dispose();
material.dispose();
texture.dispose();
renderer.dispose();

8. React와의 통합 — React Three Fiber

React 생태계에서는 React Three Fiber(R3F) 를 통해 선언적으로 Three.js를 사용할 수 있습니다:

import { Canvas } from '@react-three/fiber';
import { OrbitControls, Environment } from '@react-three/drei';

function App() {
  return (
    <Canvas camera={{ position: [0, 5, 10] }}>
      <ambientLight intensity={0.5} />
      <directionalLight position={[5, 10, 5]} />
      <mesh rotation={[0, Math.PI / 4, 0]}>
        <boxGeometry args={[2, 2, 2]} />
        <meshStandardMaterial color="royalblue" />
      </mesh>
      <OrbitControls />
      <Environment preset="sunset" />
    </Canvas>
  );
}

R3F의 장점

  • 선언적 문법: JSX로 3D 장면을 구성하여 React 개발자에게 친숙.
  • React 생태계 호환: 상태 관리(Zustand, Jotai), 라우팅 등과 자연스럽게 통합.
  • 자동 리사이즈/dispose: Canvas 크기 변경 및 메모리 정리를 자동 처리.
  • drei 라이브러리: OrbitControls, Environment, Text3D 등 자주 쓰이는 유틸리티 컴포넌트 모음.

9. 실전 활용 사례

분야 사례 핵심 기술
제품 뷰어 Nike, Apple 등 제품 3D 미리보기 GLTFLoader, OrbitControls, PBR Material
데이터 시각화 3D 차트, 지구본 기반 데이터 맵 BufferGeometry, PointsMaterial, 셰이더
인터랙티브 웹 포트폴리오, 브랜드 사이트 ScrollControls, 애니메이션, 포스트프로세싱
게임 웹 기반 캐주얼 게임 물리 엔진(Cannon.js/Rapier), InstancedMesh
AR/VR WebXR 기반 몰입형 경험 WebXRManager, XR Controllers

10. 프로젝트 시작 가이드

설치 및 기본 세팅

# npm으로 설치
npm install three

# Vite + Three.js 프로젝트 생성
npm create vite@latest my-3d-app -- --template vanilla
cd my-3d-app
npm install three
npm run dev

최소 실행 코드

import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

// 기본 설정
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x1a1a2e);

const camera = new THREE.PerspectiveCamera(
  75, window.innerWidth / window.innerHeight, 0.1, 1000
);
camera.position.set(3, 3, 3);

const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.shadowMap.enabled = true;
document.body.appendChild(renderer.domElement);

// 조명
const ambientLight = new THREE.AmbientLight(0xffffff, 0.4);
scene.add(ambientLight);

const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(5, 10, 5);
directionalLight.castShadow = true;
scene.add(directionalLight);

// 오브젝트
const cube = new THREE.Mesh(
  new THREE.BoxGeometry(1, 1, 1),
  new THREE.MeshStandardMaterial({ color: 0x6c5ce7, metalness: 0.3, roughness: 0.4 })
);
cube.castShadow = true;
cube.position.y = 0.5;
scene.add(cube);

const floor = new THREE.Mesh(
  new THREE.PlaneGeometry(10, 10),
  new THREE.MeshStandardMaterial({ color: 0x2d3436 })
);
floor.rotation.x = -Math.PI / 2;
floor.receiveShadow = true;
scene.add(floor);

// 컨트롤
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;

// 렌더 루프
function animate() {
  requestAnimationFrame(animate);
  cube.rotation.y += 0.01;
  controls.update();
  renderer.render(scene, camera);
}
animate();

// 리사이즈 대응
window.addEventListener('resize', () => {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
});

마무리

Three.js는 WebGL의 복잡성을 감추면서도 강력한 3D 그래픽 기능을 제공하는 라이브러리입니다. Scene Graph 기반의 직관적인 객체 모델, PBR 렌더링, 다양한 로더와 유틸리티를 통해 웹에서 고품질 3D 경험을 구현할 수 있습니다.

프론트엔드 개발자라면 React Three Fiber를 통해 기존 워크플로우에 자연스럽게 3D를 통합할 수 있고, 성능 최적화 기법(InstancedMesh, LOD, 텍스처 압축 등)을 적용하면 모바일 환경에서도 안정적인 렌더링이 가능합니다.

참고 자료

반응형
반응형

 

들어가며

Retrieval-Augmented Generation(RAG)은 LLM의 한계를 극복하는 가장 효과적인 패턴 중 하나입니다. LLM이 모르는 최신 정보, 사내 문서, 도메인 특화 지식을 동적으로 제공하여 환각(hallucination)을 줄이고 정확도를 높입니다.

이 글은 LLM-RAG-Study 레포지토리를 바탕으로, RAG의 기초 개념을 정리합니다.

목차

  1. RAG란 무엇인가?
  2. 환경 설정과 빠른 시작
  3. Basic RAG 파이프라인

RAG란 무엇인가?

핵심 개념

RAG는 다음 두 단계로 구성됩니다:

  1. Retrieval (검색): 질의와 관련된 문서를 벡터 DB에서 찾기
  2. Generation (생성): 검색된 문서를 컨텍스트로 LLM에 제공하여 답변 생성

왜 RAG가 필요한가?

LLM 고유의 한계:

문제 LLM만 사용 시 RAG 적용 시
시간적 한계 학습 데이터 시점 이후 정보 모름 (예: 2024년 이후 이벤트) 최신 문서 검색으로 해결
환각 (Hallucination) 모르는 내용을 그럴듯하게 지어냄 실제 문서 기반 응답으로 정확도 향상
도메인 지식 부족 사내 문서, 전문 분야 지식 제한적 자체 지식 베이스 활용
출처 추적 불가 답변의 근거 확인 어려움 검색된 문서 출처 제공 가능

RAG vs Fine-tuning

기준 RAG Fine-tuning
지식 업데이트 실시간 (문서 추가 시) 재학습 필요 (시간·비용 소모)
초기 구축 비용 낮음 높음 (GPU, 데이터 준비)
추론 지연 중간 (검색 오버헤드) 낮음
도메인 적응성 매우 높음 높음 (특정 태스크)
투명성 높음 (출처 확인 가능) 낮음 (블랙박스)

권장 전략: 대부분 RAG로 시작 → 특수 태스크만 Fine-tuning 고려

환경 설정과 빠른 시작

사전 준비

# 레포지토리 클론
git clone https://github.com/Chenjae-kr/LLM-RAG-Study.git
cd LLM-RAG-Study/01-basic-rag

# 가상환경 생성 (Python 3.8+)
python -m venv .venv

# 활성화 (macOS/Linux)
source .venv/bin/activate

# 활성화 (Windows)
.venv\Scripts\activate

# 의존성 설치
pip install -r requirements.txt

최소 요구사항

  • Python: 3.8 이상
  • 메모리: 최소 4GB (임베딩 모델 로딩)
  • 디스크: 최소 2GB (모델 캐시)
  • GPU: 선택사항 (CPU도 가능하지만 느림)

30초 데모

# 1. 샘플 문서 임베딩 (벡터 저장소 생성)
python ingest.py

# 출력 예시:
# Processing documents...
# Embedding 50 chunks...
# Vector store saved to ./vector_store

# 2. 질의 실행
python query_rag.py "인공지능이란 무엇인가요?"

# 출력:
# [Retrieved Context]
# - Document 1: "인공지능(AI)은 컴퓨터 시스템이 인간의 지능을 모방..."
#
# [Generated Answer]
# 인공지능은 기계가 인간처럼 학습하고 추론하는 기술입니다...

Basic RAG 파이프라인

1. 문서 수집 및 전처리 (Ingestion)

ingest.py 핵심 로직:

from pathlib import Path
from sentence_transformers import SentenceTransformer
import numpy as np
from utils import embed_texts, save_vector_store

# 1. 문서 로드
def load_documents(data_dir: str) -> list[str]:
    """텍스트 파일 읽기"""
    documents = []
    for file_path in Path(data_dir).glob("*.txt"):
        with open(file_path, 'r', encoding='utf-8') as f:
            documents.append(f.read())
    return documents

# 2. 청킹 (Chunking)
def chunk_text(text: str, chunk_size: int = 512, overlap: int = 50) -> list[str]:
    """텍스트를 겹치는 청크로 분할"""
    words = text.split()
    chunks = []
    for i in range(0, len(words), chunk_size - overlap):
        chunk = ' '.join(words[i:i + chunk_size])
        chunks.append(chunk)
    return chunks

# 3. 임베딩 생성
def main():
    documents = load_documents("./data")

    # 모든 문서를 청크로 분할
    all_chunks = []
    for doc in documents:
        all_chunks.extend(chunk_text(doc))

    print(f"Total chunks: {len(all_chunks)}")

    # 임베딩 모델 로드 (384차원)
    embeddings = embed_texts(all_chunks, model_name="all-MiniLM-L6-v2")

    # 벡터 저장소에 저장
    save_vector_store(embeddings, all_chunks, "./vector_store")
    print("✓ Vector store created successfully")

if __name__ == "__main__":
    main()

청킹 전략 비교:

방법 장점 단점 권장 사용처
고정 크기 구현 간단, 빠름 문맥 경계 무시 구조화된 문서
문장 단위 문맥 보존 크기 불균일 자연어 텍스트
의미 기반 의미적 일관성 높음 느림, 복잡 고품질 요구 시

권장 설정:

chunk_size = 512      # 토큰 수 (약 300-400단어)
overlap = 50          # 10% 중첩으로 문맥 연결

2. 벡터 검색 (Retrieval)

utils.py의 검색 함수:

import numpy as np
from sentence_transformers import SentenceTransformer

def search(query: str, vector_store_path: str, top_k: int = 3):
    """코사인 유사도 기반 검색"""
    # 1. 벡터 저장소 로드
    embeddings, chunks = load_vector_store(vector_store_path)

    # 2. 질의 임베딩
    model = SentenceTransformer("all-MiniLM-L6-v2")
    query_embedding = model.encode([query])[0]

    # 3. 코사인 유사도 계산
    similarities = np.dot(embeddings, query_embedding) / (
        np.linalg.norm(embeddings, axis=1) * np.linalg.norm(query_embedding)
    )

    # 4. 상위 K개 반환
    top_indices = np.argsort(similarities)[-top_k:][::-1]
    results = [(chunks[i], similarities[i]) for i in top_indices]

    return results

검색 품질 개선 팁:

  1. top_k 조정: 3-5개 권장 (너무 많으면 노이즈 증가)
  2. 유사도 임계값: 0.5 이하 결과는 필터링 고려
  3. 재랭킹: Cross-Encoder로 2차 정렬 (후술)

3. 프롬프트 구성 및 생성 (Generation)

query_rag.py:

from transformers import AutoTokenizer, AutoModelForSeq2SeqLM
from utils import search

def generate_answer(query: str, context: list[str], model_name: str = "google/flan-t5-small"):
    """컨텍스트 기반 답변 생성"""
    # 1. 검색 결과를 프롬프트로 구성
    context_text = "\n\n".join([f"[{i+1}] {text}" for i, text in enumerate(context)])

    prompt = f"""다음 문서를 참고하여 질문에 답하세요.

문서:
{context_text}

질문: {query}

답변:"""

    # 2. 모델 로드 및 생성
    tokenizer = AutoTokenizer.from_pretrained(model_name)
    model = AutoModelForSeq2SeqLM.from_pretrained(model_name)

    inputs = tokenizer(prompt, return_tensors="pt", max_length=1024, truncation=True)
    outputs = model.generate(**inputs, max_new_tokens=256)

    answer = tokenizer.decode(outputs[0], skip_special_tokens=True)
    return answer

# 메인 실행
if __name__ == "__main__":
    query = "인공지능의 주요 응용 분야는?"

    # 검색
    results = search(query, "./vector_store", top_k=3)
    contexts = [text for text, score in results]

    # 출력: 검색된 문서
    print("=== Retrieved Documents ===")
    for i, (text, score) in enumerate(results):
        print(f"\n[{i+1}] (Score: {score:.3f})")
        print(text[:200] + "...")

    # 생성
    answer = generate_answer(query, contexts)
    print("\n=== Generated Answer ===")
    print(answer)

프롬프트 엔지니어링 팁:

# ❌ 나쁜 프롬프트
prompt = f"{context}\n\n{query}"

# ✅ 좋은 프롬프트
prompt = f"""당신은 정확한 답변을 제공하는 전문가입니다.
주어진 문서에만 기반하여 답변하세요. 확실하지 않으면 "문서에 정보가 없습니다"라고 답하세요.

문서:
{context}

질문: {query}

답변 (문서 기반):"""
반응형
반응형

 

JDBC-8101:Column used in the ON clause cannot be updated. 

 

 

MERGE INTO 문을 사용하여 UPDATE와 INSERT를 진행하는 쿼리를 작성하였는데 위와 같은 오류가 발생하였다.

 

 

MERGE INTO ( ~ ) USING ( ~ ) ON ( ~ ) 문 사용 시에 조건절(ON)에서 사용된 칼럼을 UPDATE할 경우 발생하는 문제였다. UPDATE할 칼럼은 WHERE 문에서 사용해서 해결할 수 있었다.

 

 

반응형
반응형

 

  • JPA와 ORM은 뭘까?
  • 왜 JPA를 할까?

 

 

ORM이란

ORM은 데이터베이스 객체를 자바 객체로 매핑하여 객체 간의 관계를 바탕으로 SQL을 자동으로 생성해준다.

ORM은 관계형 데이터베이스의 ‘관계’를 Object에 반영하자는 것이 목적이라면, SQL Mapper는 단순히 필드를 매핑시키는 것이 목적이라는 점에서 지향점의 차이가 있다.

 

 

ORM의 시대

JPA

JPA는 자바진영에서 제공하는 API이며, 자바 ORM 기술에 대한 표준 명세이다.

자바 어플리케이션에서 관계형 데이터베이스를 사용하는 방식을 정의한 인터페이스이다. 

JPA를 구현한 ORM 프레임워크는 Hibernate, EclipseLink, DataNucleus 등이 있다.

 

 

구글트래픽을 통해 본 개발 트렌드

전세계의 구글 유저들은 압도적으로 JPA에 대한 관심이 많다.

 

 

우리나라는 아직까지 상대적으로 MyBatis에 대한 관심도가 많으나, JPA에 대한 관심이 지속적으로 증가하고 있고 슬슬 MyBatis에 대한 관심도를 추월하고 있는 것을 확인할 수 있었다.

 

 

 

 

ORM 방식은 왜 주류가 되었나?

기존의 Sql Mapper 방식(MyBatis, JdbcTemplate)에서는 개발자가 SQL문을 직접 작성해 데이터베이스를 조작했다.

따라서, 개발자가 객체에 따른 CRUD 쿼리를 반복적으로 작성해야했고 이는 유지보수의 어려움을 야기했다.

 

하지만, ORM 방식(JPA, Hibernate)에서는 객체를 통해 간접적으로 데이터베이스를 조작한다.

ORM이 객체간 관계를 바탕으로 sql을 자동으로 생성해주기 때문에 개발자는 SQL 쿼리가 아니라 Java 메서드를 통해 데이터베이스의 데이터를 조작할 수 있다.

따라서, 개발이 특정 DBMS에 종속되지 않으며, SQL문이 코드에 들어가지 않아 깔끔한 코드를 유지할 수 있다.

 

 

 


Reference

 

반응형
반응형
Serveral ports (8005,8888) required by Tomcat v8.5 Server at localhost are already in use. The server may already be running in another process, or a system process may be using the port. To start this server you will need to stop the other process or change the port number(s).

 

Tomcat 구동시 포트오류가 발생했다. 이클립스의 서버가 비정상적으로 종료될 경우에 발생한다

포트 번호 설정에서 포트를 바꿔도 되고 프로토콜 관리에서 포트를 사용하고 있는 프로세스를 종료해줘도 된다.

 

 

 

 

 

프로세스 종료하기

1. cmd 창에서 아래 명령어를 입력한다

netstat -p tcp -ano

 

 

 

2. 해당 포트 번호를 사용중인 프로세스를 찾아 pid를 확인 후 삭제해준다

taskkill /f /pid [pid_number]

위의 이미지에서는 taskkill /f /pid 21352 이다.

 

반응형
반응형

  • Spring IoC Container에 대한 설정을 변경한다.
  • Spring Bean Configuration File 작성법을 배운다.

Web Application Context

Spring Web MVC에서 사용하는 IoC 컨테이너

IoC Container란 : 자바 객체(Bean)의 라이프 사이클(생성, 초기화, 서비스, 소멸)을 관리한다

Root Web Application Context와 Dispatcher Servlet

 

https://www.baeldung.com/spring-web-contexts

 

Root Web Application Context

Root Web Application Context에서는 공유 빈을 관리한다.

Services, Repositories와 같이 특정 Dispatcher에 귀속되지 않고 공유성이 강한 객체(Bean)을 관리한다.

Root에서 생성된 객체(Bean)은 모든 Dispatcher에서 사용이 가능하다. (의존성 주입이 가능하다)

Dispatcher Servlet

Dispatcher Servlet에서는 Controller, ViewResolver, HandlerMapping와 같이

특정 Dispatcher에 귀속되는 개별성이 강한 객체(Bean)를 관리한다.

 

 

 

 

WebApplicationContext 설정

1. Web.xml

공유 객체(Bean)를 관리하는 Root Web Application Context 설정

<context-param>
	<param-name>contextConfigLocation</param-name>
	<param-value>classpath:spring/root/*.xml</param-value>
</context-param>
<listener>
	<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<context-param> : context에 parameter값을 설정

contextConfigLocation으로 xml 설정 파일의 경로를 지정한다.

Spring에서는 이 param Name으로 값을 확인하기 때문에 name은 변경할 수 없다.

<listener> : 사건이 발생 했는지 감시하는 감시자 역할

ContextLoaderListener :

어플리케이션이 시작하는 시점과 종료되는 시점을 감시한다.

WAS에서 deploy할 때와 undeploy할 때, contextConfigLocation으로 등록된 경로의 xml파일을 읽어 설정을 한다.

 

 

 

개별 객체(Bean)를 관리하는 DispatcherServlet 설정

<servlet>
	<servlet-name>appServlet</servlet-name>
	<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
	<init-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>classpath:spring/dispatcher/*.xml</param-value>
	</init-param>
	<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
	<servlet-name>appServlet</servlet-name>
	<url-pattern>/</url-pattern>
</servlet-mapping>

<servlet> : Servlet에 대한 설정

<servlet-name> : Servlet 이름

<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> : DispatcherServlet으로 등록

<init-param> : init시의 parameter값을 부여

contextConfigLocation으로 xml 설정 파일의 경로를 지정한다.

Spring에서는 이 param Name으로 값을 확인하기 때문에 name은 변경할 수 없다.

<load-on-startup> : Servlet간에 load되는 순서를 지정

<servlet-mapping> : 특정 URL 경로에 대하여 접속할 Servlet을 연결

<servlet-name> : 연결할 Servlet 이름

<url-pattern> : 연결할 특정 URL 경로, 해당 경로 아래의 모든 URL에 대하여 해당 Servlet과 연결된다.

 

 

문자 인코딩 필터 설정

POST 방식의 요청 파라미터 문자값 UTF-8 인코딩

POST 방식 요청시, param 데이터에 한글이 있으면 인코딩 오류 때문에 데이터가 깨지게 된다.

이를 해결하기 위해 Character Encoding Filter를 등록해 요청이 들어오면 Filter를 통해 데이터를 UTF-8로 인코딩시킨다.

<filter>
    <filter-name>encodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
        <param-name>encoding</param-name>
        <param-value>UTF-8</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>encodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

 

 

 

Root Web Application Context 설정

Servlet간의 공유성이 강한 객체(Service, Repository 등)를 관리하는 Root Web Application Context를 설정한다.

위에서 Root의 contextConfigLocation으로 설정한 경로에 Spring Bean Configuration File을 생성한다.

Namespaces에 beans와 context를 추가한다.

 

 
 
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="
		http://www.springframework.org/schema/beans 
		http://www.springframework.org/schema/beans/spring-beans.xsd
		
		http://www.springframework.org/schema/context 
		http://www.springframework.org/schema/context/spring-context.xsd">
	
	<!-- @Service, @Repository가 적용된 클래스만 객체로 관리 -->
	<!-- base-package 이하의 모든 패키지, use-default-filters="false" defalut 기능(전체 어노테이션 생성)을 꺼버린다 -->
	<context:component-scan base-package="com.mycompany.webapp" use-default-filters="false">
		<context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
		<context:include-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
	</context:component-scan>

</beans>

xmls:"별칭" : 추가한 Namespaces 태그 라이브러리에 대한 코드 파일을 받을 수 있는 주소, 해당 태그를 <"별칭":>으로 사용하겠다는 의미.

별칭을 주지 않을 경우에는 태그 사용시 별칭을 안붙이고 사용할 수 있다.

xsi:schemaLocation : 태그 라이브러리에 대한 정보가 담겨있어 IDE에서는 이 파일을 바탕으로 intellisense 기능을 제공한다.

Root Web Application Context에서는 공유 객체(Bean)만 관리하기 위한 설정을 추가한다.

<context:component-scan> : 객체로 등록할 Annotation을 찾는다.

base-package="" : Annotation을 찾을 시작 패키지, 해당 경로 아래의 모든 패키지를 검색한다.

use-default-filters="false" : @Controller, @Service, @Repository, @Component 어노테이션을 자동으로 등록하는 기능을 꺼버린다.

공유 객체인 Service와 Repository만 등록하기 위해서이다.

<context:include-filter> : 해당 어노테이션을 발견하면 객체(Bean)로 생성한다.

 

Dispatcher Servlet 설정

Dispatcher의 개별성이 강한 객체 (Contoller 등)을 관리하는 개별 Dispatcher Servlet을 설정한다.

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:beans="http://www.springframework.org/schema/beans"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd
		http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

	<!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->
	<!-- Enables the Spring MVC @Controller programming model -->
	<annotation-driven />

	<context:component-scan base-package="com.mycompany.webapp">
		<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
		<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
	</context:component-scan>
</beans:beans>

<mvc:annotation-driven /> : MVC 관련 어노테이션을 자동 인식 설정한다.

@RequestParam, @GetMapping, @PostMapping, @ModelAttribute를 자동 인식한다.

<context:component-scan> : 객체로 등록할 Annotation을 찾는다.

<context:exclude-filter> : 해당 어노테이션은 객체(Bean) 생성에서 제외한다.

 

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:mvc="http://www.springframework.org/schema/mvc"
	xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
	
	<!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
	<mvc:resources mapping="/resources/**" location="/resources/" />

</beans>

<mvc:resources> : Controller를 거치지 않아도 볼 수 있는 resources에 대한 경로를 지정한다.

mapping : URL 경로를 뜻한다. (http://xxxx.xxxx/resources/xxxx.jpg)

location : 프로젝트 내 webContent 이하의 파일 경로를 뜻한다.

반응형
반응형
  • 싱글톤 디자인 패턴은 뭘까?
  • 싱글톤 디자인 패턴을 어떻게 적용할까?
  • 싱글톤 디자인 패턴의 장/단점은 뭘까?

 

 

 

싱글톤 디자인 패턴이 뭐야?

시스템 상에서 어플리케이션이 실행될 때 최초 단 한 번만 객체를 메모리에 할당하고 공유해 사용하는 디자인 패턴

인스턴스가 필요할 때 기존의 생성된 인스턴스를 사용하게 한다.

DBCP (DataBase Connecting Pool)처럼 공통된 객체를 생성해서 사용해야할 때 사용한다.

 

싱글톤 디자인 패턴을 어떻게 적용할까?

싱글톤 디자인 패턴의 핵심은 객체를 단 하나만 만들고 그 객체를 공유해 사용한다는 것이다.

 

Thread Safe하게 싱글톤 패턴 적용하기

public class MockDAO {
	private static MockDAO instance = new MockDAO();
	private MockDAO() {}
	public static synchronized  MockDAO getInstance() {
		return instance;
	}
}

1. 생성자의 접근 제어자를 private으로 명시에 외부에서 객체 생성하는 것을 막는다

2. 클래스 로딩시점에 자신의 생성자를 이용해 객체를 생성하고 static 영역에 주소값을 저장한다.

3. public static Getter 메서드로 객체를 외부로 공유한다. 또한 synchronized 키워드를 이용하여 thread-safe하게 만든다.

 

하지만 이렇게 싱글톤 디자인 패턴을 적용하면, synchronized 특성상 큰 성능저하가 발생한다.

 

Holder에 의한 초기화

public class MockDAO {
    private MockDAO() {
    }
 
    private static class LazyHolder {
        public static final MockDAO INSTANCE = new MockDAO();
    }
 
    public static MockDAO getInstance() {
        return LazyHolder.INSTANCE;
    }
}

클래스 안에 Holder 클래스를 두어, JVM의 클래스 초기화 과정에서 보장되는 원자적 특성을 이용하여 객체를 재할당되지 않게 하였다. holder안에 인스턴스가 static이기 때문에 클래스 로딩시점에 한번만 호출되며 final 키워드를 사용해 값의 재할당을 막음으로써 싱글톤 디자인 패턴을 적용하였다.

 

 

싱글톤 패턴을 왜 써야하는가

하나의 객체만 생성하기 때문에 메모리 낭비를 방지할 수 있다.

또한, 두번째 이용시부터는 객체 로딩 시간을 현저히 감소시킬 수 있다.

싱글톤으로 만들어진 클래스는 전역 인스턴스이기 때문에 다른 클래스의 인스턴스들이 데이터를 공유하기 쉽다.

 

 

싱글톤 패턴 적용시 주의해야할 점

싱글톤 인스턴스가 너무 많은 일을 할 경우에는 다른 클래스의 인스턴스들과 결합도가 높아져 수정과 테스트가 어려워진다. 따라서, 꼭 필요한 경우가 아니라면 지양하는 것이 좋다.

 

 


Reference

 

 

 

반응형
반응형

  • 객체가 아닌 데이터를 파일로 관리할 순 없을까?
  • Properties 파일에서 의존성 주입은 어떻게 할까?



properties 파일 경로 지정

1. properties 파일 생성

service.prop1=10
service.prop2=10.5
service.prop3=false
service.prop4=문자열

2. Root Web Application Context의 설정을 담당하는 xml에 properties파일의 경로를 지정한다.

<context:property-placeholder location="classpath:properties/*.properties"/>

 

XML을 이용한 property 의존성 주입

Properties 값을 이용해 DI
index : 생성자에 여러 매개변수가 있을 경우, 매개변수의 순서를 지정
value = ${propertiesKey} : property-placeholder에 있는 자료 중 key에 해당하는 value값이 대입

<bean id="ch13Service5" class="com.mycompany.webapp.service.Ch13Service5">
    <constructor-arg index="0" value="${service.prop1}"/>
    <constructor-arg index="1" value="${service.prop2}"/>
    <property name="prop3" value="${service.prop3}"/>
</bean>
public class Ch13Service5 {
	private static final Logger logger = LoggerFactory.getLogger(Ch13Service5.class);
	
	private int prop1;
	private double prop2;
	private boolean prop3;

	public Ch13Service5(int prop1, double prop2) {
		logger.info("실행");
		logger.info("prop1: " + prop1);
		logger.info("prop2: " + prop2);
	}

	public void setProp3(boolean prop3) {
		logger.info("실행");
		logger.info("prop3: " + prop3);
		this.prop3 = prop3;
	}
}

 

애너테이션을 이용한 property DI

@Value 어노테이션을 이용하여 Properties파일에 등록된 property값을 DI할 수 있다.
@PropertySource("classpath: ~ ") 어노테이션을 이용하여 properties 파일의 위치를 명시해준다.
생성자에 DI시, 정의된 기본 생성자가 없을 경우에는 Spring은 매개변수가 있는 생성자에 DI할 수 있는지 확인하고 할 수 있다면 해당 생성자를 사용한다.

@Service
@PropertySource("classpath:properties/ch13_di.properties")
public class Ch13Service6 {
	private static final Logger logger = LoggerFactory.getLogger(Ch13Service6.class);
	
	private int prop1;
	private double prop2;
	private boolean prop3;
	
	//field
	@Value("${service.prop4}") private String prop4;
	
	//생성자
	public Ch13Service6(
			@Value("${service.prop1}") int prop1,
			@Value("${service.prop2}") double prop2) {
		logger.info("실행");
		logger.info("prop1: " + prop1);
		logger.info("prop2: " + prop2);
		this.prop1 = prop1;
		this.prop2 = prop2;
	}
	
	//Setter
	@Value("${service.prop3}")
	public void setProp3(boolean prop3) {
		logger.info("실행");
		logger.info("prop3: " + prop3);
		this.prop3 = prop3;
	}
}

반응형
반응형

  • 어노테이션을 통한 의존성 주입(DI)는 어떻게 할까?
  • @Resource, @Autowired, @Inject의 차이는 뭘까?

 

 

클래스간의 의존 관계를 주입할 때에는 주로 어노테이션 방식으로 의존성 주입을 하게 된다.

따라서 개발자가 가장 자주 사용하게 될 의존성 주입 방식은 어노테이션 방식의 의존성 주입이다.

 

의존성 주입에 관련된 어노테이션은 @Resource@Autowired / @Inject가 있는데 차이는 뭘까?

 

 

@Resource

스프링이 관리하는 빈 이름(Bean Name)으로 객체를 찾아 해당 빈을 주입한다.

이름으로 빈을 찾지 못하면 타입을 기준으로 의존성 주입을 진행한다.

Setter, Field에 사용할 수 있지만 생성자에는 사용하지 못 한다.

인터페이스로 다형성을 적용한 구현체들 중 하나를 선택해야하는 경우에는 객체명을 명시해준다.

Java 9 이상에서는 사용할 수 없다.

 

 

Setter에 애너테이션

@Service
public class Ch13Service2 {
    @Resource
	public void setCh13BoardDao2(Ch13BoardDao2 ch13BoardDao2) {
		logger.info("실행");
		this.ch13BoardDao2 = ch13BoardDao2;
	}
    ...
}

Field에 애너테이션

@Service
public class Ch13Service2 {
	@Resource(name="ch13BoardDao4")
	private Ch13BoardDao2 ch13BoardDao2;

    ...
}

 

 

@Autowired

IOC Controller에서 빈을 찾을 때,  데이터 타입을 기준으로 객체를 찾고 주입된다

동일한 타입의 빈이 여러 개 존재할 경우에는 참조 변수의 이름과 동일한 빈을 찾아서 주입한다

생성자, Setter, Field에 사용할 수 있다. 즉, @Resource와는 달리 생성자에도 사용할 수 있다

@Qualifier 애너테이션을 사용해서 이름을 기준으로 의존성 주입할 빈을 지정 가능

동일한 타입의 빈이 여러 개 존재할 경우 @Primary 애너테이션을 빈 클래스에 적용하면 우선 주입될 빈을 지정 가능

 

생성자에 애너테이션

@Service
public class Ch13Service2 {
    @Autowired
	public Ch13Service2(Ch13BoardDao2 ch13BoardDao2) {
		logger.info("실행");
        this.ch13BoardDao2 = ch13BoardDao2;
	}
    ...
}

Setter에 애너테이션

@Service
public class Ch13Service2 {
    @Autowired
	public void setCh13BoardDao2(Ch13BoardDao2 ch13BoardDao2) {
		logger.info("실행");
		this.ch13BoardDao2 = ch13BoardDao2;
	}
    ...
}

Field에 애너테이션

@Service
public class Ch13Service2 {
	@Autowired
	private Ch13BoardDao2 ch13BoardDao2;

    ...
}

 

@Inject

@Inject 애너테이션은 @Autowired와 동일하게 사용할 수 있으며 서로 대체가 가능하다.

추가적으로 빈 이름으로 의존성을 주입할 때 @Named 애너테이션도 사용할 수 있다. 

@Autowired는 스프링에서만 지원하는 애너테이션으로 스프링 프레임워크에 종속적이지만 @Inject은 Java에서 지원하는 애너테이션으로 스프링에 종속적이지 않다.

또한, @Inject은 nullable하지 않기 때문에 무조건 빈을 주입받아야한다.

 

 

 

 


 

Rerference

 

 

 

반응형

+ Recent posts