포맷 나열 방법 – 완전 감지 가이드

Java에서 문서를 처리하려다 라이브러리가 해당 형식을 지원하지 않아 벽에 부딪힌 적이 있나요? 당신만 그런 것이 아닙니다. 파일 형식 호환성은 UnsupportedFileException이라고 외치기 전에 프로젝트를 좌절시킬 수 있는 gotcha 순간 중 하나입니다.

포맷 나열 방법을 아는 것은 견고한 문서 처리 시스템을 구축하는 데 필수적입니다. 문서 관리 플랫폼, 파일 변환 서비스 구축이든, 혹은 validate document upload java가 필요하든, 프로그래밍 방식의 형식 감지는 런타임 예외와 불만을 예방해 줍니다.

이 가이드에서는 check file format java를 확인하고, 파일 타입을 java로 검색하며, GroupDocs.Comparison을 사용해 실제 Java 애플리케이션에 이러한 검사를 통합하는 방법을 알아봅니다.

빠른 답변

  • 포맷을 나열하는 기본 메서드는? FileType.getSupportedFileTypes()는 현재 라이브러리 버전이 처리할 수 있는 모든 형식을 반환합니다.
  • API 사용에 라이선스가 필요합니까? 예 — 개발을 위해서는 무료 체험 또는 임시 라이선스가 필요하고, 운영 환경에서는 상용 라이선스가 필요합니다.
  • 포맷 목록을 캐시할 수 있나요? 물론입니다 — 캐싱을 통해 형식 메타데이터 로드에 드는 일회성 오버헤드를 줄일 수 있습니다.
  • 포맷 감지가 스레드‑안전한가요? 예, GroupDocs API는 스레드‑안전합니다; 단지 자체 캐시가 동시성을 처리하도록 하면 됩니다.
  • 라이브러리 업데이트 시 목록이 변경되나요? 새 릴리스에서는 종종 형식이 추가됩니다; 최신 상태를 유지하려면 업그레이드 후에 다시 캐시하세요.

Java 애플리케이션에서 파일 형식 감지가 중요한 이유

지원되는 형식을 조기에 감지하면 런타임 오류를 방지하고, 불필요한 CPU 사이클을 줄이며, 사용자가 업로드할 수 있는 파일에 대해 즉시 피드백을 제공할 수 있습니다. 무거운 처리를 시작하기 전에 호환성을 확인함으로써 서비스 응답성을 유지하고 오류 로그를 깔끔하게 유지할 수 있습니다.

형식 감지가 도움이 되는 일반 시나리오:

  • 업로드 검증 – 지원되지 않는 파일을 가장 먼저 차단합니다.
  • 배치 처리 – 실패를 일으킬 파일을 건너뛰어 배치를 계속 진행합니다.
  • API 통합 – 일반적인 500 오류 대신 명확한 오류 메시지를 반환합니다.
  • 리소스 계획 – 알려진 형식 특성을 기반으로 CPU와 메모리를 추정합니다.
  • 사용자 경험 – 파일 선택기에서 지원되는 확장자를 간결하게 표시합니다.

비즈니스 영향

똑똑한 형식 감지는 단순한 기술적 편리함을 넘어 직접적인 비즈니스 가치를 제공합니다:

  • 지원 티켓 감소: 사용자는 사전에 어떤 파일이 가능한지 알 수 있습니다.
  • 리소스 활용 효율화: 호환 가능한 파일만 처리해 다른 작업에 CPU를 할당합니다.
  • 만족도 향상: 명확한 피드백이 좌절감을 없앱니다.
  • 개발 주기 가속: 초기 검증으로 QA 이전에 버그를 잡아냅니다.

전제 조건 및 설정 요구 사항

필요 사항

개발 환경

  • Java Development Kit (JDK) 8 이상
  • Maven 또는 Gradle (의존성 관리)
  • 선호하는 IDE (IntelliJ IDEA, Eclipse, VS Code)

지식 전제 조건

  • 기본 Java 문법 및 OOP 개념
  • Maven/Gradle 프로젝트 구조에 대한 이해
  • Java 예외 처리에 대한 이해

라이브러리 의존성

  • GroupDocs.Comparison for Java (추가 방법을 보여드림)

GroupDocs를 한 번도 사용해 본 적이 없어도 걱정 마세요—모든 단계를 함께 진행합니다.

GroupDocs.Comparison for Java 설정

왜 GroupDocs.Comparison인가?

GroupDocs.Comparison은 70개 이상의 입력 및 출력 형식을 지원하며, 클래식 Office 파일부터 CAD 도면, 이메일 아카이브까지 포괄합니다. 단일하고 일관된 API를 제공하므로 여러 라이브러리를 뒤섞어 사용할 필요가 없습니다.

Maven 설치

pom.xml에 다음 저장소와 의존성을 추가하세요:

<repositories>
   <repository>
      <id>repository.groupdocs.com</id>
      <name>GroupDocs Repository</name>
      <url>https://releases.groupdocs.com/comparison/java/</url>
   </repository>
</repositories>

<dependencies>
   <dependency>
      <groupId>com.groupdocs</groupId>
      <artifactId>groupdocs-comparison</artifactId>
      <version>25.2</version>
   </dependency>
</dependencies>

Gradle 설정

Gradle 사용자는 build.gradle에 다음을 추가하세요:

repositories {
    maven {
        url 'https://releases.groupdocs.com/comparison/java/'
    }
}

dependencies {
    implementation 'com.groupdocs:groupdocs-comparison:25.2'
}

라이선스 구성 옵션

개발용

  • 무료 체험 – 평가에 적합하며 신용카드가 필요 없습니다.
  • 임시 라이선스 – 개발 단계에서 전체 기능을 사용할 수 있습니다.

운영용

  • 상용 라이선스 – 실 서비스 배포 시 반드시 필요합니다.

: 먼저 무료 체험으로 모든 필요한 형식이 나열되는지 확인한 뒤, 코딩을 마무리하면서 임시 라이선스로 전환하세요.

포맷 나열 방법

시작 시 FileType.getSupportedFileTypes()를 한 번 호출하고, 반환된 컬렉션을 캐시한 뒤, HashSet<String>을 사용해 들어오는 파일을 O(1) 시간에 검증합니다. 이 API를 활용하면 하드코딩된 목록을 피하고 향후 라이브러리 업데이트와의 호환성을 보장할 수 있습니다. 이 한 줄 호출만으로 GroupDocs.Comparison이 처리할 수 있는 모든 형식의 완전하고 버전‑정확한 목록을 얻을 수 있습니다.

핵심 구현

FileType 클래스는 확장자, MIME 타입, 기능 플래그 등을 포함하는 단일 파일 형식의 표현입니다.

import com.groupdocs.comparison.result.FileType;

// Retrieve the iterable collection of supported file types
Iterable<FileType> fileTypes = FileType.getSupportedFileTypes();

// Iterate over each file type in the collection
for (FileType fileType : fileTypes) {
    // Print out the file type to demonstrate retrieval
    System.out.println(fileType);
}

// Indicate successful retrieval of supported file types
System.out.println("\nSupported file types retrieved successfully.");

코드 이해

무슨 일이 일어나나요

  1. FileType.getSupportedFileTypes()는 라이브러리가 알고 있는 모든 형식을 포함하는 Iterable<FileType>을 반환합니다.
  2. FileType 객체는 getExtension(), getMimeType(), isSupportedForComparison()와 같은 속성을 제공합니다.
  3. 루프는 각 형식의 확장자와 간단한 설명을 출력합니다.

이 접근 방식의 주요 장점

  • 런타임 발견 – 유지 보수해야 할 하드코딩된 목록이 없습니다.
  • 버전 호환성 – 목록은 사용 중인 JAR의 정확한 기능을 항상 반영합니다.
  • 동적 검증 – API 출력으로 직접 검증 로직을 구축합니다.

필터링을 포함한 향상된 구현

운영 환경에서는 종종 형식을 필터링해야 합니다(예: 비교에만 지원되는 형식 또는 오피스 문서만). 다음 패턴은 재사용 가능한 필터링된 Set<String>을 만드는 방법을 보여줍니다.

import com.groupdocs.comparison.result.FileType;
import java.util.*;

public class FormatDetector {
    
    public static Map<String, List<String>> categorizeFormats() {
        Map<String, List<String>> categories = new HashMap<>();
        categories.put("Documents", new ArrayList<>());
        categories.put("Spreadsheets", new ArrayList<>());
        categories.put("Presentations", new ArrayList<>());
        categories.put("Images", new ArrayList<>());
        categories.put("Other", new ArrayList<>());
        
        Iterable<FileType> fileTypes = FileType.getSupportedFileTypes();
        
        for (FileType fileType : fileTypes) {
            String extension = fileType.getExtension().toLowerCase();
            String category = determineCategory(extension);
            categories.get(category).add(extension);
        }
        
        return categories;
    }
    
    private static String determineCategory(String extension) {
        if (extension.matches("\\.(doc|docx|pdf|txt|rtf)")) {
            return "Documents";
        } else if (extension.matches("\\.(xls|xlsx|csv)")) {
            return "Spreadsheets";
        } else if (extension.matches("\\.(ppt|pptx)")) {
            return "Presentations";
        } else if (extension.matches("\\.(jpg|jpeg|png|gif|bmp)")) {
            return "Images";
        }
        return "Other";
    }
}

일반 설정 문제 및 해결책

문제 1: 의존성 해결 오류

증상: Maven/Gradle이 GroupDocs 저장소나 아티팩트를 찾지 못합니다.

해결책

  • 네트워크가 repo.groupdocs.com에 대한 HTTPS outbound를 허용하는지 확인하세요.
  • 저장소 URL 철자를 다시 확인하세요.
  • 기업 환경에서는 내부 Nexus 또는 Artifactory 미러에 저장소를 추가하세요.

빠른 해결

<!-- Add to Maven settings.xml if repository access is restricted -->
<mirrors>
    <mirror>
        <id>central-proxy</id>
        <mirrorOf>*</mirrorOf>
        <url>http://your-corporate-nexus/repository/maven-public/</url>
    </mirror>
</mirrors>

문제 2: 라이선스 검증 오류

증상: 애플리케이션은 실행되지만 라이선스 경고가 기록되거나 기능이 제한됩니다.

해결책

  • .lic 파일을 클래스패스(src/main/resources 등)에 배치하세요.
  • 라이선스가 만료되지 않았으며 제품 버전과 일치하는지 확인하세요.
  • 체험판을 사용하는 경우 30일 후에 만료된다는 점을 기억하세요.

라이선스 로딩 코드 예시

// Load license at application startup
License license = new License();
license.setLicense("path/to/GroupDocs.Comparison.lic");

문제 3: 런타임에 ClassNotFoundException 발생

증상: 코드는 컴파일되지만 실행 시 클래스 누락 오류가 발생합니다.

주요 원인

  • 충돌하는 전이 의존성(예: 오래된 commons-logging 버전을 끌어오는 다른 라이브러리).
  • 라이브러리 최소 요구사항보다 낮은 JDK 버전 사용.

디버깅 단계

  1. mvn dependency:tree(또는 gradle dependencies)를 실행해 충돌을 찾습니다.
  2. JDK 8 이상인지 확인합니다.
  3. 필요하면 문제를 일으키는 전이 의존성을 제외합니다.

문제 4: 대형 포맷 목록으로 인한 성능 문제

증상: getSupportedFileTypes() 첫 호출이 이후 호출보다 눈에 띄게 오래 걸립니다.

해결책: 결과를 스레드‑안전한 싱글톤(예: EnumMap 또는 ConcurrentHashMap 사용)으로 캐시하세요. 목록은 JVM 수명 동안 변하지 않으므로 한 번만 로드하면 반복적인 리플렉션 오버헤드를 없앨 수 있습니다.

public class FormatCache {
    private static volatile List<FileType> cachedFormats;
    
    public static List<FileType> getSupportedFormats() {
        if (cachedFormats == null) {
            synchronized (FormatCache.class) {
                if (cachedFormats == null) {
                    cachedFormats = new ArrayList<>();
                    FileType.getSupportedFileTypes().forEach(cachedFormats::add);
                }
            }
        }
        return cachedFormats;
    }
}

실제 애플리케이션을 위한 통합 패턴

패턴 1: 사전 업로드 검증

파일이 서버에 도달하기 전에 check file format java를 수행해야 하는 웹 앱에 적합합니다.

public class FileUploadValidator {
    
    private static final Set<String> SUPPORTED_EXTENSIONS = 
        getSupportedExtensions();
    
    public boolean isSupported(String filename) {
        String extension = getExtension(filename).toLowerCase();
        return SUPPORTED_EXTENSIONS.contains(extension);
    }
    
    private static Set<String> getSupportedExtensions() {
        Set<String> extensions = new HashSet<>();
        FileType.getSupportedFileTypes().forEach(
            type -> extensions.add(type.getExtension().toLowerCase())
        );
        return extensions;
    }
    
    private String getExtension(String filename) {
        int lastDot = filename.lastIndexOf('.');
        return lastDot > 0 ? filename.substring(lastDot) : "";
    }
}

패턴 2: 형식 필터링을 포함한 배치 처리

batch process file formats가 필요할 때, 이 패턴은 지원되지 않는 파일을 우아하게 건너뛰고 나중에 검토할 수 있도록 로그에 기록합니다.

public class BatchProcessor {
    
    public ProcessingResult processBatch(List<File> files) {
        Map<String, List<File>> categorized = categorizeFiles(files);
        
        ProcessingResult result = new ProcessingResult();
        result.setProcessedFiles(processSupported(categorized.get("supported")));
        result.setSkippedFiles(categorized.get("unsupported"));
        
        return result;
    }
    
    private Map<String, List<File>> categorizeFiles(List<File> files) {
        Set<String> supportedExts = getSupportedExtensions();
        
        return files.stream().collect(
            Collectors.groupingBy(file -> 
                supportedExts.contains(getExtension(file.getName())) 
                    ? "supported" : "unsupported"
            )
        );
    }
}

패턴 3: REST API 형식 정보 제공

클라이언트 애플리케이션이 허용된 확장자를 동적으로 렌더링할 수 있도록 list supported file types 엔드포인트를 노출합니다.

@RestController
@RequestMapping("/api/formats")
public class FormatController {
    
    @GetMapping("/supported")
    public ResponseEntity<List<FormatInfo>> getSupportedFormats() {
        List<FormatInfo> formats = new ArrayList<>();
        
        FileType.getSupportedFileTypes().forEach(type -> {
            formats.add(new FormatInfo(
                type.getExtension(),
                type.getFileFormat(),
                determineDescription(type)
            ));
        });
        
        return ResponseEntity.ok(formats);
    }
    
    @GetMapping("/check/{extension}")
    public ResponseEntity<SupportInfo> checkFormat(@PathVariable String extension) {
        boolean supported = isFormatSupported(extension);
        return ResponseEntity.ok(new SupportInfo(extension, supported));
    }
}

운영 환경 사용을 위한 모범 사례

메모리 관리

현명하게 캐시: 지원 형식 목록을 static final 필드나 전용 캐시 제공자(예: Caffeine)에 저장하세요. 메타데이터는 몇 킬로바이트에 불과하지만 반복적인 리플렉션은 비용이 발생할 수 있습니다.

// Good: Initialize once, use many times
private static final List<FileType> SUPPORTED_FORMATS = 
    StreamSupport.stream(FileType.getSupportedFileTypes().spliterator(), false)
                 .collect(Collectors.toList());

// Avoid: Calling getSupportedFileTypes() repeatedly

오류 처리

우아한 다운그레이드: 형식 감지가 실패하면(예: 손상된 JAR) 최소한의 하드코딩된 목록으로 대체하고 경고를 기록하세요. 예외가 사용자 인터페이스까지 전파되지 않도록 합니다.

public boolean isFormatSupported(String filename) {
    try {
        String extension = getExtension(filename);
        return SUPPORTED_FORMATS.stream()
            .anyMatch(type -> type.getExtension().equalsIgnoreCase(extension));
    } catch (Exception e) {
        // Log the error but don't fail the operation
        logger.warn("Format check failed for: " + filename, e);
        return false; // Conservative approach
    }
}

성능 최적화

지연 초기화: 실제로 필요할 때까지 형식 목록 로드를 미루세요. 이는 문서를 전혀 다루지 않을 수도 있는 마이크로서비스의 시작 시간을 단축합니다.

public class LazyFormatChecker {
    private volatile boolean initialized = false;
    private Set<String> supportedExtensions;
    
    public boolean isSupported(String extension) {
        ensureInitialized();
        return supportedExtensions.contains(extension.toLowerCase());
    }
    
    private void ensureInitialized() {
        if (!initialized) {
            synchronized (this) {
                if (!initialized) {
                    loadSupportedExtensions();
                    initialized = true;
                }
            }
        }
    }
}

구성 관리

형식 제한 외부화: application.yml 또는 properties 파일에 비즈니스 유닛별 허용 확장자를 정의하세요. 이렇게 하면 코드 재배포 없이 정책을 변경할 수 있습니다.

# application.yml
document-processing:
  allowed-formats:
    - pdf
    - docx
    - xlsx
  max-file-size: 10MB
  validation-mode: strict

고급 사용 사례 및 응용

엔터프라이즈 문서 관리

대규모 조직은 부서별 허용 목록이 필요합니다. FileType 메타데이터와 역할 기반 접근 제어를 결합하면 “법무팀은 PDF와 DOCX만 업로드 가능, 마케팅팀은 PPTX도 가능”과 같은 세밀한 정책을 구현할 수 있습니다.

클라우드 스토리지 통합

AWS S3, Azure Blob, Google Drive와 같은 서비스에서 파일을 동기화할 때, 다운로드하기 전에 지원되지 않는 형식을 필터링하면 대역폭을 절약하고 저장 비용을 줄일 수 있습니다.

자동화 워크플로 시스템

비즈니스 프로세스 자동화는 형식에 따라 문서를 라우팅할 수 있습니다. 예를 들어 계약 검토 워크플로는 DOCX만 허용하고, 청구서 처리 파이프라인은 PDF, XLSX, CSV를 허용합니다.

성능 고려 사항 및 최적화

메모리 사용 최적화

전체 형식 메타데이터를 메모리에 로드하는 비용은 저렴합니다(≈ 5 KB). 그러나 수십 개의 마이크로서비스를 제한된 컨테이너에서 실행한다면:

  1. 지연 로드 – 필요할 때만 로드합니다.
  2. 선택적 캐시 – 실제 지원하는 형식(예: 오피스 문서)만 유지합니다.
  3. WeakReference 캐시 사용 – 메모리 압박 시 JVM이 회수하도록 합니다.

CPU 성능 팁

  • 캐시된 확장자를 기반으로 HashSet<String>을 사용해 상수 시간 조회를 수행합니다.
  • 파일명 검증에 사용하는 정규식은 미리 컴파일합니다.
  • 대규모 배치 작업에서는 I/O 제한을 고려하면서 parallelStream()을 활용해 병렬 처리합니다.
// Efficient format validation
private static final Set<String> SUPPORTED_EXTENSIONS = 
    Collections.unmodifiableSet(loadSupportedExtensions());

public boolean isSupported(String extension) {
    return SUPPORTED_EXTENSIONS.contains(extension.toLowerCase());
}

확장성 고려 사항

  • 애플리케이션 시작: Spring Bean의 @PostConstruct 메서드에서 형식 목록을 초기화합니다.
  • 분산 캐시: 클러스터 환경에서는 Redis 또는 Hazelcast를 통해 캐시된 목록을 공유해 각 노드가 별도로 로드하지 않도록 합니다.
  • 연결 풀링: 추가 검증을 위해 외부 서비스를 호출할 경우 HikariCP와 같은 풀을 사용해 지연 시간을 낮춥니다.

일반 런타임 문제 해결

문제: 일관되지 않은 형식 감지 결과

증상: 동일한 파일 확장자가 때때로 지원되지 않는다고 표시됩니다.

근본 원인

  • 노드마다 다른 라이브러리 버전 사용.
  • 특정 프리미엄 형식을 비활성화하는 라이선스 제한.
  • 중복된 JAR로 인한 클래스 로더 혼란.

디버깅 접근법

  1. 시작 시 GroupDocs.Comparison 버전을 로그에 남깁니다(VersionInfo.getVersion()).
  2. 모든 서버에 동일한 라이선스 파일이 배포되었는지 확인합니다.
  3. java -verbose:class를 실행해 라이브러리가 한 번만 로드되는지 확인합니다.

문제: 시간이 지남에 따라 성능 저하

증상: 몇 시간 운영 후 형식 감시가 느려집니다.

일반 원인

  • 커스텀 캐시가 메모리 누수로 계속 성장.
  • 임시 FileType 객체를 저장하는 ArrayList가 무제한 확장.
  • 큰 힙 압력으로 인한 GC 정지.

해결책

  • 커스텀 캐시에 LRU와 같은 퇴거 정책을 적용합니다.
  • JVisualVM 등으로 힙 사용량을 모니터링합니다.
  • Java Flight Recorder로 핫스팟을 프로파일링합니다.

문제: 형식 감지가 조용히 실패

증상: 예외는 발생하지 않지만 일부 형식이 목록에 나타나지 않습니다.

조사 단계

  1. com.groupdocs에 대한 디버그 로깅을 활성화합니다(log4j.logger.com.groupdocs=DEBUG).
  2. 라이선스 초기화가 성공했는지 확인합니다(License.isValid()).
  3. 누락된 형식이 프리미엄 애드온에 속하는지 확인하고, 필요 시 상위 티어 라이선스로 업그레이드합니다.

결론 및 다음 단계

포맷 나열 방법을 이해하는 것은 단일 API 호출을 넘어, 탄탄하고 사용자 친화적인 문서 파이프라인의 기반이 됩니다. 런타임 감지, 캐싱, 견고한 오류 처리를 통합하면 버그를 크게 줄이고 고객에게 원활한 경험을 제공할 수 있습니다.

핵심 체크리스트

  • FileType.getSupportedFileTypes()를 한 번 호출하고 결과를 캐시한 뒤 HashSet으로 조회합니다.
  • 무거운 처리를 시작하기 전에 업로드를 검증해 CPU를 절약하고 UX를 개선합니다.
  • 라이선스를 최신 상태로 유지하고, 새 릴리스에서는 추가 형식이 제공됩니다.
  • 허용 목록을 외부화해 비즈니스 규칙을 코드 변경 없이 업데이트합니다.

다음 작업

  1. 기존 업로드 서비스에 핵심 감지 스니펫을 추가합니다.
  2. 싱글톤 캐시를 구현합니다(예: Spring @Cacheable 사용).
  3. 아키텍처에 맞는 통합 패턴(사전 업로드, 배치, REST) 중 하나를 선택합니다.
  4. 대표 데이터셋으로 성능 벤치마크를 실행해 O(1) 조회 속도를 확인합니다.

더 알고 싶나요? GroupDocs.Comparison의 고급 기능(예: 나란히 비교, 메타데이터 추출, 대량 비교 작업)을 탐색해 진정한 엔터프라이즈급 문서 워크플로를 구축하세요.

자주 묻는 질문

Q: 지원되지 않는 파일 형식을 처리하려 하면 어떻게 되나요?
A: GroupDocs.Comparison은 UnsupportedFileFormatException을 발생시킵니다. getSupportedFileTypes()로 사전 검증하면 비용이 많이 드는 처리를 시작하기 전에 문제를 차단할 수 있습니다.

Q: 라이브러리 버전 간에 지원 형식 목록이 변경되나요?
A: 예. 각 새 릴리스마다 추가 형식이 포함되며, 마이너 버전당 보통 3‑5개의 새로운 형식이 추가됩니다. 업그레이드 후에는 반드시 다시 캐시하세요.

Q: 라이브러리를 확장해 추가 형식을 지원할 수 있나요?
A: 지원 형식 목록은 릴리스당 고정됩니다. 특수 형식이 필요하면 GroupDocs.Comparison을 전문 서드파티 파서와 결합하거나 맞춤형 애드온을 위해 GroupDocs에 문의하세요.

Q: 형식 감지는 얼마나 많은 메모리를 사용하나요?
A: 메타데이터 자체는 약 5 KB 정도입니다. 실제 메모리 사용량은 캐시 컬렉션을 어떻게 저장하고 공유하느냐에 따라 달라지며, 단순 HashSet<String>은 거의 무시할 수준의 오버헤드만 추가합니다.

Q: 형식 감지는 스레드‑안전한가요?
A: 예, FileType.getSupportedFileTypes()는 스레드‑안전합니다. 자체 캐시(예: static ConcurrentHashMap)도 동시 읽기/쓰기를 안전하게 처리하도록 설계하세요.

Q: 형식 지원 여부를 확인하는 성능 영향은?
A: 초기 호출은 서버당 약 10‑15 ms 정도 소요됩니다. 이후 조회는 O(1)이며 0.1 ms 이하로 완료됩니다.


마지막 업데이트: 2026-07-20
테스트 환경: GroupDocs.Comparison 25.2 for Java
작성자: GroupDocs

추가 리소스

관련 튜토리얼