Avalon의 간략한 역사와 Avalon을 만든 모든 설계 원칙에 대한 개요
모든 것은 Apache JServ 프로젝트에서 시작되었습니다. Apache JServ 개발에 도움을 준 Stefano Mazzocchi와 다른 사람들은 프로젝트에 사용된 패턴 중 일부가 서버 프레임워크를 만드는 데 사용할 수 있을 만큼 일반적이라는 것을 깨달았습니다. 1999년 1월 27일 수요일(JServ 1.0b가 출시된 지 약 한 달 후) Stefano는 Java Apache Server Framework라는 프로젝트를 시작하자는 제안을 내놓았습니다. 그 목표는 Apache의 모든 Java 서버 코드의 기반이 되는 것입니다. 아이디어는 프레임워크를 제공하여 일부 구성 요소를 중앙 집중화하고 프로젝트 전체에서 코드를 재사용하는 것입니다.
Stefano Mazzocchi, Federico Barbieri 및 Pierpaolo Fumagalli가 원본 버전을 만들었습니다. 2000년 후반에 Berin Loritsch와 Peter Donald가 프로젝트에 합류했습니다. 그 무렵 Pierpaolo와 Stefano는 다른 프로젝트 개발에 눈을 돌렸고 Java Apache Server Framework는 Avalon으로 불리기 시작했습니다. 이 5명의 개발자는 현재 버전의 프레임워크에 사용되는 디자인과 개념을 주로 담당합니다. 현재 버전은 2000년 6월에 출시된 버전과 매우 유사합니다. 실제로 가장 큰 차이점은 패키지를 재구성하고 프로젝트를 하위 프로젝트로 나누는 것입니다. 오늘날에도 동일한 디자인 패턴과 인터페이스가 존재합니다.
Avalon이란 무엇인가요?
Avalon은 Framework, Excalibur, LogKit, Phoenix 및 Cornerstone이라는 5개 하위 프로젝트의 상위 프로젝트입니다. Avalon이라고 하면 대부분의 사람들은 Framework를 생각하지만 Avalon에는 Framework 이상의 내용이 포함되어 있습니다. Avalon은 프레임워크, 도구, 구성 요소 및 서버 코어 구현이 모두 하나의 프로젝트로 구성된 Java Apache Server Framework로 시작되었습니다. Avalon의 각 부분은 성숙도 수준과 릴리스 주기가 다르기 때문에 Avalon을 앞서 언급한 소규모 프로젝트로 나누기로 결정했습니다. 이렇게 하면 새로운 개발자가 이전에는 거의 불가능했던 Avalon의 다양한 부분을 더 쉽게 이해하고 배울 수 있습니다. Framework
Avalon Framework는 Avalon 산하의 다른 모든 프로젝트의 기반입니다. 이는 Avalon의 인터페이스, 계약 및 기본 구현을 정의합니다. 프레임워크는 대부분의 작업을 프레임워크에 배치하므로 가장 성숙한 프로젝트이기도 합니다.
Excalibur
Avalon Excalibur는 자신의 프로젝트에서 사용할 수 있는 서버 측 구성 요소 세트입니다. 여기에는 풀링 구현, 데이터베이스 연결 관리 및 기타 구성 요소 관리 구현이 포함됩니다.
LogKit
Avalon LogKit은 Framework, Excalibur, Cornerstone 및 Phoenix에서 사용하는 고속 로깅 도구 세트입니다. 해당 모델은 JDK 1.4 Logging 패키지와 동일한 원칙을 사용하지만 JDK 1.2+와 호환됩니다.
Phoenix
Avalon Phoenix는 서비스(블록이라고 불리는 서버 측 구성 요소로 구현되는 서비스)의 출시 및 실행을 관리하는 서버의 핵심입니다.
Cornerstone
Avalon Cornerstone은 Phoenix 환경에 배포할 수 있는 블록 또는 서비스 집합입니다. 이러한 블록에는 블록 간 소켓 관리 및 작업 예약이 포함됩니다.
Scratchpad
Scratchpad는 실제로 공식 프로젝트는 아니지만 아직 엑스칼리버에 넣을 준비가 되지 않은 구성 요소를 위한 준비 영역입니다. 이러한 구성 요소의 품질은 크게 다르며 해당 API는 Excalibur 프로젝트로 승격될 때까지 변경되지 않은 상태로 유지된다는 보장이 없습니다.
이 개요의 주요 내용
이 개요에서는 Avalon Framework에 중점을 두지만 시작하기에 앞서 Avalon Excalibur 및 Avalon LogKit에 대해서도 충분히 다룹니다. Avalon을 실제로 사용하는 방법을 보여주기 위해 가상의 비즈니스 서버를 사용하겠습니다. 완전하고 포괄적인 방법론을 정의하거나 모든 하위 프로젝트의 모든 측면을 제시하는 것은 이 개요의 범위를 벗어납니다. 우리는 다른 모든 프로젝트의 기초가 되는 Avalon Framework에 중점을 둡니다. 프레임워크를 이해할 수 있다면 Avalon 기반의 모든 프로젝트를 이해할 수 있습니다. 또한 Avalon에서 일반적으로 사용되는 몇 가지 일반적인 프로그래밍 관용어(idiom)에 익숙해지게 됩니다. 프레임워크에 초점을 맞추고 Avalon Excalibur 및 Avalon LogKit 프로젝트를 참여시키는 또 다른 이유는 공식적으로 출시되고 지원된다는 것입니다.
아발론은 어디에서 사용할 수 있나요?
아발론이 어떤 용도에 적합하고 어떤 용도에 적합하지 않은지 명확히 하라는 질문을 여러 번 받았습니다. Avalon은 서버 측 프로그래밍에 중점을 두고 있으며 서버 애플리케이션 중심 프로젝트를 보다 쉽게 설계하고 유지 관리할 수 있도록 해줍니다. Avalon은 구현을 포함하는 프레임워크로 설명할 수 있습니다. Avalon은 서버 측 솔루션에 중점을 두고 있지만 많은 사람들은 Avalon이 일반 애플리케이션에도 유용하다고 생각합니다. Framework, Excalibur 및 LogKit에서 사용되는 개념은 모든 프로젝트에 적용할 수 있을 만큼 일반적입니다. 서버에 더욱 직접적으로 초점을 맞춘 두 가지 프로젝트는 Cornerstone과 Phoenix입니다. 프레임워크
1. 지원 또는 폐쇄형 구조 2. 아이디어가 포함된 기본 시스템 또는 배열
프레임워크라는 단어는 응용 프로그램에서 광범위한 의미를 갖습니다. 제약 시스템, 통신 시스템 등 단일 산업에 초점을 맞춘 프레임워크를 수직 시장 프레임워크라고 합니다. 그 이유는 동일한 프레임워크가 다른 산업에는 적합하지 않기 때문입니다. 매우 다재다능하고 여러 산업에서 사용할 수 있는 프레임워크를 수평적 시장 프레임워크라고 합니다. Avalon은 수평적 시장 프레임워크입니다. Avalon의 프레임워크를 사용하여 수직 시장 프레임워크를 구축할 수 있습니다. Avalon으로 구축된 수직 시장 프레임워크의 가장 설득력 있는 예는 Apache Cocoon 출판 프레임워크입니다. Apache Cocoon 버전 2는 Avalon의 Framework, Excalibur 및 LogKit 프로젝트를 사용하여 구축되었습니다. 이는 프레임워크의 인터페이스와 계약을 활용하므로 개발자가 Cocoon의 작동 방식을 이해하는 데 소요되는 시간을 줄일 수 있습니다. 또한 Excalibur에서 제공하는 데이터 소스 관리 및 구성 요소 관리 코드를 효율적으로 사용하므로 바퀴를 다시 만들 필요가 없습니다. 마지막으로 LogKit을 사용하여 게시 프레임워크의 모든 로깅 문제를 처리합니다. Avalon Framework의 기본 원칙을 이해하면 Avalon을 기반으로 구축된 모든 시스템을 이해할 수 있습니다. 시스템을 이해하면 프레임워크의 오용으로 인해 발생하는 결함을 더 빠르게 찾아낼 수 있습니다. 마법의 공식은 없습니다
도구를 성공의 마법 공식으로 사용하려는 모든 시도는 문제를 야기한다는 점을 언급할 가치가 있습니다. 아발론도 예외는 아닙니다. Avalon의 프레임워크는 서버측 솔루션용으로 설계되었기 때문에 이를 사용하여 그래픽 사용자 인터페이스(GUI)를 구축하는 것은 좋은 생각이 아닙니다. Java에는 이미 Swing이라는 GUI 구축을 위한 프레임워크가 있습니다. Avalon이 귀하의 프로젝트에 적합한지 고려해야 하지만 여전히 Avalon의 원리와 디자인에서 뭔가를 배울 수 있습니다. 스스로에게 물어봐야 할 질문은 "프로젝트가 어디에 사용될 것인가?"입니다. 서버 환경에서 실행될 것이라는 대답이 나온다면 Java Servlet을 생성하든 특수 목적으로 생성하든 Avalon이 좋은 선택이 될 것입니다. 서버 응용 프로그램입니다. 대답이 고객의 컴퓨터에서 실행되고 서버와 상호 작용이 없다는 것이라면 Avalon이 적합하지 않을 수 있습니다. 그럼에도 불구하고 구성 요소 모델은 매우 유연하며 대규모 응용 프로그램의 복잡성을 관리하는 데 도움이 됩니다.
Principles and Patterns
Avalon은 몇 가지 특정 디자인 원칙을 기반으로 제작되었습니다. 가장 중요한 두 가지 모드는 통제 반전(Inversion of Control)과 우려 사항 분리(Separation of Concerns)입니다. 구성 요소 지향 프로그래밍, 측면 지향 프로그래밍 및 서비스 지향 프로그래밍도 Avalon에 영향을 미칩니다. 각 프로그래밍 원칙은 책의 양을 채울 수 있지만 모두 디자인 사고 습관입니다. 제어 반전
제어 반전(IOC) 개념은 구성 요소가 항상 외부에서 관리된다는 의미입니다. 이 문구는 브라이언 푸트(Brian Foote)가 그의 논문 중 하나에서 처음 사용했습니다. 구성 요소에 필요한 모든 것은 컨텍스트, 구성 및 로거를 통해 구성 요소에 제공됩니다. 실제로 구성 요소 수명 주기의 모든 단계는 구성 요소를 생성하는 코드에 의해 제어됩니다. 이 패턴을 사용하면 구성 요소가 시스템과 안전하게 상호 작용하는 방법을 구현합니다. IOC는 보안과 동의어가 아닙니다! IOC는 확장 가능한 보안 모델을 구현할 수 있는 메커니즘을 제공합니다. 시스템이 진정으로 안전하려면 모든 구성 요소가 안전해야 하고, 어떤 구성 요소도 전달된 개체의 내용을 수정할 수 없으며, 모든 상호 작용은 알려진 엔터티를 사용해야 합니다. 보안은 주요 관심사이며 IOC는 보안 목표를 달성하기 위한 프로그래머의 무기고 도구입니다.
관심의 분리
시스템을 다양한 사고 방향에서 바라봐야 한다는 생각이 우려의 분리(SOC) 패턴으로 이어졌습니다. 동일한 문제 공간에 대해 다양한 관점에서 웹 서버를 살펴보는 것이 한 예입니다. 웹 서버는 안전하고 안정적이며 관리 가능하고 구성 가능해야 하며 HTTP 사양을 충족해야 합니다. 각 속성은 별도의 고려사항입니다. 이러한 고려 사항 중 일부는 보안 및 안정성(서버가 불안정하면 안전할 수 없음)과 같은 다른 고려 사항과 관련되어 있습니다. 별도 고려 모델은 AOP(Aspect Oriented 프로그래밍)로 이어졌습니다. 연구자들은 클래스나 메서드 세분성에서는 많은 고려 사항을 해결할 수 없다는 사실을 발견했습니다. 이러한 고려 사항을 측면이라고 합니다. 측면의 예로는 객체 수명 주기 관리, 로깅, 예외 처리, 리소스 정리 및 해제 등이 있습니다. 안정적인 AOP 구현이 없기 때문에 Avalon 개발 팀은 구성 요소로 구현되는 몇 가지 작은 인터페이스를 제공하여 측면이나 고려 사항을 구현하기로 결정했습니다.
컴포넌트 지향 프로그래밍
컴포넌트 지향 프로그래밍(Component Oriented 프로그래밍, COP)은 시스템을 일부 컴포넌트나 기능으로 나누는 아이디어입니다. 각 시설에는 작동하는 인터페이스와 해당 인터페이스를 둘러싼 계약이 있습니다. 이 접근 방식을 사용하면 시스템의 다른 부분에 있는 코드에 영향을 주지 않고 구성 요소 인스턴스를 쉽게 교체할 수 있습니다. OOP(객체 지향 프로그래밍)와 COP의 주요 차이점은 통합 수준입니다. 클래스 간의 상호 의존성이 적어 COP 시스템의 복잡성을 관리하기가 더 쉽습니다. 이렇게 하면 코드 재사용 정도가 높아집니다. COP의 주요 이점 중 하나는 프로젝트 코드의 일부를 수정해도 전체 시스템이 손상되지 않는다는 것입니다. 또 다른 이점은 구성 요소를 여러 개 구현하고 런타임 시 그 중에서 선택할 수 있다는 것입니다.
서비스 지향 프로그래밍
서비스 지향 프로그래밍(SOP)의 개념은 시스템을 시스템이 제공하는 일부 서비스로 나누는 것입니다. Service
1. 타인을 위해 수행되는 작업 또는 업무 2. 수리 또는 유지 관리를 제공하는 시설 3. 대중에게 도구를 제공하는 시설
Avalon의 Phoenix는 모든 시설을 특정 인터페이스와 관련 계약으로 구성된 서비스로 제공한다고 간주합니다. 서비스 구현을 블록이라고 합니다. 서버 프로그램은 여러 서비스로 구성되어 있다는 점을 이해하는 것이 중요합니다. 메일 서버를 예로 들면 프로토콜 처리 서비스, 인증 및 권한 부여 서비스, 관리 서비스 및 핵심 메일 처리 서비스가 있습니다. Avalon의 Cornerstone은 귀하의 시스템에서 활용할 수 있는 몇 가지 낮은 수준의 서비스를 제공합니다. 제공되는 서비스에는 연결 관리, 소켓 관리, 참가자/역할 관리 및 스케줄링 등이 포함됩니다. 여기서는 가상 시스템을 여러 시설로 분해하는 과정과 관련된 서비스를 소개합니다.
시스템 분석
구성 요소를 구성하는 요소를 어떻게 결정합니까? 핵심은 솔루션이 효율적으로 작동하는 데 필요한 시설을 정의하는 것입니다.
가설의 비즈니스 서버를 사용하여 서비스와 구성 요소를 식별하고 결정하는 방법을 보여 드리겠습니다. 시스템에서 사용하는 일부 서비스를 정의한 후 이러한 서비스 중 하나를 예로 들어 해당 서비스에 필요한 다양한 구성 요소를 정의합니다. 내 목표는 시스템을 관리 가능한 부분으로 정의하는 데 도움이 되는 몇 가지 개념을 전달하는 것입니다.
시스템 분석 - 구성 요소 식별
완전하고 포괄적인 방법론을 제공하는 것은 이 문서의 범위를 벗어나지만 몇 가지 문제에 대해 논의하고 싶습니다. 우리는 구성요소와 서비스의 구현 중심 정의부터 시작하여 실용적인 정의를 제공할 것입니다. 구성 요소
구성 요소는 작업자 인터페이스와 해당 작업자 인터페이스 구현의 조합입니다. 구성 요소를 사용하면 개체 간의 느슨한 결합이 제공되므로 이를 사용하는 코드에 영향을 주지 않고 구현을 변경할 수 있습니다.
서비스
서비스는 하나 이상의 구성 요소로 구성되어 완벽한 솔루션을 제공합니다. 서비스의 예로는 프로토콜 처리기, 작업 스케줄러, 인증 및 권한 부여 서비스 등이 있습니다.
이 정의는 출발점을 제공하지만 완전한 그림을 제공하지는 않습니다. 시스템(프로젝트를 구성하는 일련의 시설로 정의됨)을 필요한 구성 요소로 분해하려면 하향식 접근 방식을 사용하는 것이 좋습니다. 이 접근 방식은 사용 가능한 항목을 정확히 알기 전에 세부 사항에 얽매이는 것을 방지합니다. 프로젝트의 범위를 결정하세요
프로젝트에서 어떤 기능을 달성할지 결정하세요. 처음에는 항상 일반적인 아이디어를 갖고 있어야 합니다. 비즈니스 세계에서는 초기 작업 명세서가 그 역할을 수행합니다. 오픈 소스 세계에서는 일반적으로 아이디어나 브레인스토밍 프로세스를 통해 이를 수행합니다. 저는 프로젝트에 대해 높은 수준의 관점을 갖는 것의 중요성은 아무리 강조해도 지나치지 않다고 생각합니다. 대규모 프로젝트는 다양한 서비스로 구성되는 반면 소규모 프로젝트에는 하나 또는 두 개의 서비스만 포함된다는 것은 분명합니다. 약간 압도당하는 느낌이 들기 시작하면 큰 프로젝트는 실제로 하나의 큰 우산 아래에 있는 많은 작은 프로젝트라는 점을 기억하십시오. 결국 전체 시스템의 큰 그림을 이해할 수 있게 될 것입니다.
작업 설명: 비즈니스 서버
비즈니스 서버(Business Server)는 가상의 프로젝트입니다. 논의의 목적에 따라 그 기능은 판매 주문을 처리하고, 고객에게 자동으로 청구서를 발행하고, 재고 관리를 관리하는 것입니다. 판매 주문은 도착 시 일종의 거래 시스템을 통해 처리되어야 합니다. 서버는 판매 주문이 체결된 후 30일 후에 자동으로 고객에게 청구서를 발행합니다. 재고는 서버와 공장이나 창고의 현재 재고를 통해 관리됩니다. 비즈니스 서버는 분산 시스템이 되며, 각 서버는 메시지 서비스를 통해 다른 서버와 통신합니다.
서비스 발견
우리는 이 비즈니스 서버 프로젝트를 사용하여 서비스를 발견할 것입니다. 위의 지나치게 일반적인 작업 설명을 고려하면 프로젝트 설명에 정의된 일부 서비스를 즉시 볼 수 있습니다. 서비스 목록은 명시적 서비스(작업 설명에서 직접 파생될 수 있는 서비스)와 암시적 서비스(유사 작업을 기반으로 검색된 서비스 또는 명시적 서비스를 지원하는 서비스)의 두 가지 광범위한 범주로 나눌 수 있습니다. 시스템을 구현하는 회사가 모든 서비스를 직접 개발할 필요는 없습니다. 일부는 상용 솔루션으로 구입할 수 있습니다. 그러한 경우, 우리는 결정론적인 방식으로 상용 제품과 상호 운용할 수 있는 래퍼를 개발할 수 있습니다. 시스템을 구현하는 회사가 대부분의 서비스를 구축하게 됩니다. 명시적 서비스
작업 명세서에서 일부 서비스를 빠르게 내보낼 수 있습니다. 그러나 이러한 초기 분석은 일부 서비스의 정의를 보장하기 위해 다른 서비스의 존재를 요구하기 때문에 작업이 완료되었음을 의미하지 않습니다. 거래 처리 서비스
직무 설명에는 "판매 주문이 도착하는 대로 처리되어야 한다"고 명확하게 명시되어 있습니다. 이는 판매 요청을 수락하고 자동으로 처리하는 메커니즘이 필요함을 의미합니다. 이는 웹 서버의 작동 방식과 유사합니다. 리소스에 대한 요청을 받아 처리하고 결과(예: HTML 페이지)를 반환합니다. 이를 트랜잭션 처리라고 합니다. 완료하려면 다양한 유형의 거래가 있습니다. 이러한 일반 거래 처리 서비스는 "판매 주문 처리자"와 같이 좀 더 구체적인 서비스로 분류되어야 할 가능성이 높습니다. 정확한 방법은 서비스의 일반성에 따라 다릅니다. 유용성과 재사용성 사이에는 균형이 있습니다. 서비스가 일반적일수록 재사용 가능성이 높아집니다. 또한 일반적으로 이해하기가 더 어렵습니다.
스케줄링 서비스
경우에 따라 거래가 완료되면 일정 시간이 지난 후 이벤트를 예약해야 하는 경우도 있습니다. 또한 재고 관리 프로세스에서는 정기적으로 구매 주문을 발행할 수 있어야 합니다. 직무 설명에 "판매 주문이 체결된 후 30일이 지나면 서버가 자동으로 고객에게 청구서를 발행합니다"라고 명시되어 있으므로 예약 서비스가 필요합니다. 다행스럽게도 Avalon Cornerstone은 우리가 직접 작성할 필요가 없도록 이를 제공합니다.
메시지 서비스
작업 설명에는 분산 시스템에서 "각 서버는 메시지 서비스를 통해 다른 서버와 통신합니다."라고 명시되어 있습니다. 생각해 봅시다. 때로는 사용자가 특정 제품이나 사용하고 싶은 방법을 원할 때도 있습니다. 메시징 서비스는 타사 제품을 활용한 대표적인 예입니다. 아마도 우리는 메시징 서비스의 인터페이스로 JMS(Java Messaging Service)를 사용할 것입니다. JMS는 표준이므로 인터페이스가 조만간 변경될 가능성은 거의 없습니다. 실제 경험에 따르면 잘 정의된 메시지 지향 시스템은 객체 지향 시스템(예: EJB)보다 확장성이 뛰어납니다. 확장성이 더 나은 이유 중 하나는 메시지의 동시 메모리 오버헤드가 일반적으로 적기 때문입니다. 또 다른 이유는 모든 처리를 소규모 서버 클러스터(또는 단일 서버)에 집중하는 것보다 메시지 처리 로드를 모든 서버에 분산시키는 것이 더 쉽다는 것입니다.
재고관리 서비스
교과서에 나오는 클래식 서비스는 아니지만, 이 시스템에서는 꼭 필요한 서비스입니다. 재고 관리 서비스는 공장 또는 창고 재고 기록을 지속적으로 모니터링하고 재고가 부족해지기 시작하면 이벤트를 트리거합니다.
암시적 서비스
과거 시스템에서 얻은 경험을 활용하여 시스템을 다른 서비스로 더욱 분해하면 명시적으로 지적되지는 않았지만 시스템에 필요한 일부 서비스를 얻을 수 있습니다. 공간적 제약으로 인해 포괄적인 분석은 하지 않습니다. 인증 및 권한 부여 서비스
인증 및 권한 부여 서비스는 직무 설명에 명시적으로 언급되어 있지 않지만 모든 비즈니스 시스템은 보안을 심각하게 고려해야 합니다. 이는 시스템의 모든 클라이언트가 인증되어야 하고 모든 사용자 작업이 승인되어야 함을 의미합니다.
워크플로 자동화 서비스
워크플로 자동화는 엔터프라이즈 시스템에서 널리 사용되는 개발 영역입니다. 타사 워크플로 관리 서버를 사용하지 않는 경우 직접 작성해야 합니다. 일반적으로 워크플로 자동화는 소프트웨어 시스템을 사용하여 회사의 비즈니스 프로세스 전반에 걸쳐 작업을 예약하는 것입니다. 자세한 내용은 Workflow Management Council 웹사이트(http://www.wfmc.org/)를 참조하세요.
문서센터 서비스
작업의 현재 상태 정보로서 "문서센터"라는 단어의 정의는 매우 부정확합니다. 즉, 회사가 구매 주문을 받으면 우리 시스템은 구매 주문 정보를 저장하고 불러올 수 있어야 합니다. 청구에는 인벤토리부터 신규 사용자 요청까지 시스템의 다른 프로세스와 동일한 요구 사항이 있습니다.
요약
비즈니스 서버 프로젝트의 서비스 예제가 더 많은 것을 발견하는 데 도움이 되기를 바랍니다. 더 높은 추상화 계층에서 더 낮은 추상화 계층으로 이동하면 열린 포트에서 요청을 처리하기 위한 연결 서비스와 같은 더 많은 유형의 서비스가 필요하다는 것을 알게 될 것입니다. 우리가 정의한 일부 서비스는 메시징 서비스, 작업 흐름 관리 서비스 등 제3자 시스템을 통해 구현됩니다. 이러한 서비스의 경우 나중에 공급자를 변경할 수 있도록 표준 인터페이스를 사용하는 것이 가장 좋습니다. 일부 서비스는 실제로 여러 서비스로 구성된 대규모 서비스입니다. 일부 서비스는 Avalon Excalibur 또는 Avalon Cornerstone에서 이미 제공됩니다. 시스템에서 서비스를 검색할 때 명심해야 할 한 가지는 서비스가 상위 수준 하위 시스템이어야 한다는 것입니다. 이는 분석가 팀을 통해 구성 요소를 정의하는 데 도움이 됩니다. 주요 서비스를 식별했으므로 여러 개인(또는 팀)이 각 서비스를 병렬로 분류하도록 할 수 있습니다. 하위 시스템 경계도 잘 정의되어 있으며 중복될 가능성이 거의 없습니다. 병렬 분석을 수행하기로 결정한 경우 다시 돌아가서 공통 구성 요소를 식별하여 가능한 한 많이 재사용할 수 있도록 해야 합니다.
비즈니스 서버용 UML 다이어그램
구성 요소 검색
앞서 언급한 문서 센터 서비스를 예로 들어 적절한 구성 요소를 식별하는 프로세스를 설명하겠습니다. 논의를 위해 이제 문서 센터 서비스에 대한 요구 사항을 나열합니다. 문서 센터는 데이터베이스를 영구 저장소로 사용하고 클라이언트에 권한을 부여하며 문서를 메모리에 캐시합니다. 컴포넌트의 실제 정의
구성 요소에 대해 이야기할 때 다음과 같은 측면에서 생각해야 합니다. 내 서비스가 작동하려면 어떤 시설이 필요한가? Avalon은 시스템 캐스팅의 개념을 믿습니다. 시스템 개발자는 역할이라고 하는 구성 요소에 대한 책임 목록에 직면하게 됩니다. 역할이란 무엇인가요?
역할의 개념은 연극에서 유래합니다. 연극, 뮤지컬, 영화에는 배우가 연기하는 특정 수의 캐릭터가 있습니다. 배우가 부족한 것 같지는 않지만 역할 수는 제한되어 있습니다. 쇼의 대본은 캐릭터의 기능이나 행동을 정의합니다. 극장에서 일어나는 것과 마찬가지로 스크립트는 구성 요소와 상호 작용하는 방식을 결정합니다. 시스템의 다양한 액터에 대해 생각하고 액터에 구성요소를 투영하고 그들과 대화합니다. 역할은 구성 요소 클래스에 대한 계약입니다. 예를 들어, Document Center 서비스에는 데이터베이스 운영이 필요합니다. Avalon Excalibur는 "데이터 소스" 역할의 요구 사항을 충족하는 구성 요소를 정의합니다. Excalibur에는 두 가지 구성 요소가 있으며 둘 다 이 역할의 요구 사항을 충족합니다. 어떤 것을 사용할지는 서비스가 있는 환경에 따라 다르지만 모두 동일한 계약을 충족합니다. 많은 Avalon 기반 시스템은 캐릭터당 하나의 활성 구성 요소만 사용합니다. 스크립트는 작업 인터페이스, 즉 다른 구성 요소가 상호 작용하는 인터페이스입니다. 컴포넌트의 인터페이스를 결정할 때는 명확한 계약을 맺고 이를 염두에 두어야 합니다. 계약은 구성 요소 사용자가 제공해야 하는 것과 구성 요소가 생산하는 것을 규정합니다. 때로는 사용 의미 체계가 계약에 포함되어야 합니다. 예를 들어 임시 저장소 구성 요소와 영구 저장소 구성 요소 간의 차이가 있습니다. 인터페이스와 프로토콜이 정의되면 이를 구현하는 작업을 수행할 수 있습니다.
좋은 후보 컴포넌트는 무엇인가요?
문서 센터 서비스에서는 DataSourceComponent(Excalibur에서), Cache, Repository, Guardian의 네 가지 가능한 컴포넌트를 식별했습니다. 상호 작용이 원활하게 이루어질 수 있는 여러 구현을 가질 가능성이 있는 역할을 찾아야 합니다. 이 예를 통해 대체 시설을 사용해야 하는 상황이 있음을 알 수 있습니다. 대부분의 경우 이 기능의 구현을 하나만 사용하지만 시스템의 나머지 부분에 영향을 주지 않고 독립적으로 업그레이드할 수 있어야 합니다. 다른 경우에는 환경에 따라 다른 구현을 사용해야 합니다. 예를 들어 Excaliber에서 정의한 "데이터 소스"는 일반적으로 모든 JDBC 연결 풀링 자체를 처리하지만 때로는 J2EE(Java 2 Enterprise Edition)에서 제공되는 기능을 활용하고 싶을 수도 있습니다. Excalibur는 하나의 "데이터 소스" 구성 요소가 JDBC 연결과 풀을 직접 관리하고 다른 구성 요소가 Java의 JNDI(Naming and Directory Interface)를 사용하여 특정 연결을 얻도록 하여 이 문제를 해결합니다.
좋은 컴포넌트가 아닌 것은 무엇인가요?
JavaBeans를 사용하는 데 익숙한 사람들은 모든 것을 JavaBean으로 구현하는 것을 좋아합니다. 이는 데이터 모델부터 트랜잭션 처리까지 모든 것을 의미합니다. 이러한 방식으로 구성 요소에 접근하면 시스템이 지나치게 복잡해질 수 있습니다. 구성 요소를 데이터 모델이 아닌 서비스 또는 시설 모델로 생각하십시오. 다른 소스에서 데이터를 가져오는 구성 요소가 있을 수 있지만 데이터는 여전히 데이터로 유지되어야 합니다. Avalon Excalibur의 이러한 철학의 예는 연결이 구성 요소가 아니라는 것입니다. 또 다른 예는 앞서 언급한 Guardian 구성 요소입니다. Guardian에 포함된 논리는 Document Center 서비스와 너무 관련성이 높기 때문에 완전히 다른 서비스의 구성 요소로 사용할 수 없다고 주장할 수 있습니다. 복잡성을 관리하는 방법과 유연성을 높이는 방법은 많지만 추가 작업을 수행할 가치가 없는 경우도 있습니다. 이 경우 신중하게 결정을 내려야 합니다. 잠재적인 구성 요소의 논리가 일관되게 적용된다면 이를 구성 요소로 처리하는 것이 합리적일 수 있습니다. 시스템에는 구성 요소의 인스턴스가 여러 개 있을 수 있으며 런타임 시 선택할 수 있습니다. 기본 구성 요소의 논리가 다른 구성 요소에 의해서만 결정되는 경우 논리를 다른 구성 요소에 넣는 것이 가능할 수 있습니다. Guardian 컴포넌트와 Repository 컴포넌트의 예를 통해 Guardian이 너무 Repository에 집중하고 컴포넌트로 구현되지 않는다고 주장할 수 있습니다.
문서 센터 서비스 분석
구현될 구성 요소를 해당 역할, 근본 원인 및 소스(구성 요소가 이미 존재하는 경우)와 함께 나열합니다. DocumentRepository
DocumentRepository는 전체 서비스의 상위 구성 요소입니다. Avalon에서는 서비스가 특정 유형의 구성 요소인 블록으로 구현됩니다. 블록에는 서비스 마커 인터페이스를 확장하는 작동 인터페이스가 있어야 합니다. Block 인터페이스는 Avalon의 Component 인터페이스도 확장합니다. Block 및 Service는 Avalon Phoenix에 포함된 인터페이스입니다. 마지막으로 서비스는 여전히 기술적으로 특정 유형의 구성 요소입니다. DocumentRepository는 영구 저장소에서 Document 객체를 얻는 방법입니다. 보안, 기능 및 속도를 제공하기 위해 서비스의 다른 구성 요소와 상호 작용합니다. 이 특정 DocumentRepository는 데이터베이스에 연결하고 내부적으로 데이터베이스 논리를 사용하여 Document 개체를 만듭니다.
DataSourceComponent
DataSourceComponent는 Avalon Excalibur에서 제공됩니다. 이것이 유효한 JDBC 연결 개체를 얻는 방법입니다.
캐시
캐시는 단기 메모리 내 저장 시설입니다. DocumentRepository는 이를 사용하여 Document 객체를 저장하고 해싱 알고리즘을 통해 참조합니다. Cache 구성요소의 재사용성을 향상시키기 위해 저장된 객체는 Cacheable 인터페이스를 구현해야 합니다.
Guardian
Guardian 구성 요소의 역할은 참가자 관리 권한을 기반으로 합니다. Guardian은 데이터베이스에서 라이선스 규칙 세트를 로드합니다. Guardian은 표준 Java 보안 모델을 사용하여 특정 문서에 대한 액세스를 보장합니다.
요약
지금쯤이면 무엇이 좋은 구성 요소를 만드는지 어느 정도 알고 계실 것입니다. 이 예에서는 문서 센터 서비스의 모든 구성 요소를 설명하고 해당 구성 요소가 수행할 작업을 간략하게 소개합니다. 이 목록을 간략하게 살펴보면 시설을 데이터가 아닌 구성 요소로 구현하는 접근 방식이 나와 있습니다. 이제 서비스가 작동하는 데 필요한 구성 요소를 결정할 수 있습니다.
프레임워크 및 기초
실제로 구성 요소를 작성할 수 있는 기반을 마련하기 위해 Avalon의 계약과 인터페이스를 설명합니다.
Avalon Framework는 전체 Avalon 프로젝트의 핵심 부분입니다. 프레임워크에 의해 정의된 계약과 구조를 이해하면 해당 프레임워크를 활용하는 모든 코드를 이해할 수 있습니다. 우리가 논의한 원칙과 패턴을 기억하십시오. 이 섹션에서는 역할 개념이 실제로 어떻게 작동하는지, 구성 요소의 수명주기 및 인터페이스가 작동하는 방식을 자세히 설명합니다.
구성요소의 역할 정의
Avalon에서는 모든 구성요소가 역할을 합니다. 그 이유는 역할을 통해 구성요소를 얻기 때문입니다. 이 경기장에서 우리가 고려해야 할 유일한 것은 캐릭터의 서명입니다. Part 2에서 컴포넌트를 "작업 인터페이스와 해당 작업 인터페이스 구현의 조합"으로 정의했던 것을 기억해 보세요. 작업 인터페이스가 역할입니다. 역할에 대한 인터페이스 만들기
아래에서는 몇 가지 모범 사례와 그 이유와 함께 인터페이스의 예를 볼 수 있습니다. package org.apache.bizserver.docs;public 인터페이스 DocumentRepository는 Component{ String ROLE = DocumentRepository.class.getName(); Document getDocument(Principal requestor, int refId);}
Best practice
· "ROLE"이라는 파일을 포함합니다. 캐릭터의 공식 이름인 문자열입니다. 이 이름은 작업자 인터페이스의 정규화된 이름과 동일합니다. 이는 나중에 구성 요소의 인스턴스를 가져와야 할 때 도움이 될 것입니다. · 가능하다면 구성 요소 인터페이스를 확장하십시오. 이렇게 하면 구성 요소를 게시할 때 더 쉬워집니다. 작업 인터페이스를 제어할 책임이 없다면 이는 아무 소용이 없습니다. 게시할 때 항상 구성 요소의 인스턴스로 캐스팅할 수 있으므로 큰 문제는 아닙니다. · 한 가지 일을 하고 그것을 잘 하라. 컴포넌트의 인터페이스는 최대한 단순해야 합니다. 작업 인터페이스가 다른 인터페이스를 확장하는 경우 구성 요소의 계약을 이해하기 어렵게 만듭니다. 오래된 미국 약어는 이 점을 잘 표현합니다: Keep It Simple, Stupid(KISS). 자신보다 더 똑똑해지고(그리고 더 어리석어지는) 것은 어렵지 않습니다. 나도 몇 번 해본 적이 있습니다. · 필요한 방법만 식별하십시오. 클라이언트 프로그램은 구현 세부 사항을 알 수 없으며 대체 방법이 너무 많으면 불필요한 복잡성만 초래할 것입니다. 즉, 한 가지 방법을 선택하고 이를 고수하십시오. · 캐릭터 인터페이스가 수명주기나 생존 인터페이스를 연장하지 않도록 하세요. 그러한 클래스나 인터페이스를 구현하는 경우 사양을 구현하려고 합니다. 이는 좋은 패턴이 아니며 향후 디버깅 및 구현 문제만 일으킬 것입니다.
캐릭터 이름을 선택하세요
아발론에서는 모든 캐릭터에게 이름이 있습니다. 이는 시스템의 다른 구성 요소에 대한 참조를 얻는 방법입니다. Avalon 개발팀은 캐릭터 이름 지정에 대한 몇 가지 규칙을 설명했습니다. 명명 규칙
· 작업 인터페이스의 정규화된 이름은 일반적으로 역할 이름입니다. 예외는 이러한 일반 규칙 아래에 나열되어 있습니다. 이 예에서 이론적 구성 요소 이름은 "org.apache.bizserver.docs.DocumentRepository"여야 합니다. 이는 인터페이스의 "ROLE" 속성에 포함되어야 하는 이름입니다. · 컴포넌트 선택기를 통해 컴포넌트에 대한 참조를 얻는 경우 일반적으로 첫 번째 규칙에서 추론된 역할 이름과 끝에 "Selector"라는 단어를 사용합니다. 이 명명 규칙의 결과는 "org.apache.bizserver.docs.DocumentRepositorySelector"입니다. DocumentRepository.ROLE + "Selector"를 통해 이 이름을 얻을 수 있습니다. · 동일한 작업 인터페이스를 구현하지만 다른 목적을 제공하는 여러 구성 요소가 있는 경우 역할을 분리합니다. 역할은 시스템에서 구성 요소의 목적입니다. 각 캐릭터 이름은 원래 캐릭터 이름으로 시작되지만, 캐릭터의 목적을 나타내는 이름은 /${목적} 형식으로 추가됩니다. 예를 들어, DocumentRePository에 대해 BuyOrder(구매 주문) 및 Bill(청구서) 목적을 가질 수 있습니다. 이 두 역할은 각각 DocumentRepository.ROLE + "/PurchaseOrder" 및 DocumentRepository.ROLE + "/Bill"로 표현될 수 있습니다.
프레임워크 인터페이스 개요
전체 Avalon Framework는 API에 따라 활동, 구성 요소, 구성, 컨텍스트, 로거, 매개 변수, 스레드 및 기타의 7가지 주요 범주로 나눌 수 있습니다. 각 카테고리(기타 제외)는 관심 영역을 나타냅니다. 구성 요소는 일반적으로 관심 있는 고려 사항의 방향을 나타내기 위해 여러 인터페이스를 구현합니다. 이를 통해 구성 요소 컨테이너는 각 구성 요소를 일관된 방식으로 관리할 수 있습니다. Avalon 인터페이스의 수명주기
프레임워크가 구성 요소의 다양한 측면을 개별적으로 고려하기 위해 여러 인터페이스를 구현하는 경우 메서드 호출 순서에 대해 혼동이 발생할 가능성이 있습니다. Avalon Framework는 이를 실현하여 이벤트 수명주기 순서에 대한 프로토콜을 개발했습니다. 구성 요소가 관련 인터페이스를 구현하지 않으면 처리할 다음 이벤트로 이동합니다. 컴포넌트를 생성하고 준비하는 올바른 방법이 있기 때문에 이벤트가 수신될 때 컴포넌트를 설정할 수 있습니다. 구성 요소의 수명 주기는 초기화 단계, 활동 서비스 단계 및 파괴 단계의 세 단계로 나뉩니다. 이러한 단계는 순차적으로 발생하므로 이러한 이벤트를 순서대로 논의하겠습니다. 또한 Java 언어 때문에 생성 및 종료 동작이 암시적으로 수행되므로 생략하겠습니다. 메소드 이름과 필요한 인터페이스를 나열하겠습니다. 각 단계에는 메서드 이름으로 식별되는 단계가 있습니다. 구성 요소가 괄호 안에 지정된 인터페이스를 확장하는 경우 이러한 단계는 순차적으로 수행됩니다. 초기화 단계
다음 단계는 순차적으로 발생하며 구성 요소 수명 동안 한 번만 발생합니다. 1. 활성화Logging() [LogEnabled] 2. contextualize() [컨텍스트화 가능] 3. compose() [구성 가능] 4.configure() [구성 가능] 또는 매개변수화() [매개변수화 가능] 5. 초기화() [초기화 가능] 6. 시작 () [시작 가능]
Active Service Phase
다음 단계는 순차적으로 발생하지만 구성 요소의 수명 동안 여러 번 발생할 수 있습니다. Suspendable 인터페이스를 구현하지 않기로 선택한 경우 re로 시작하는 단계를 수행할 때 올바른 기능을 보장하는 것은 구성 요소의 책임입니다. 1. suspens() [일시 중지 가능] 2. recontextualize() [재컨텍스트화 가능] 3. recompose() [재구성 가능] 4. reconfigure() [재구성 가능] 5. 이력서() [일시 중지 가능]
파괴 단계
다음 단계는 순차적으로 발생합니다. , 구성 요소 수명 동안 한 번만 발생합니다. 1. stop() [Startable] 2. dispose() [Disposable]
Avalon Framework Contract
이 섹션에서는 가장 중요한 부분인 맨 앞에 배치하는 컴포넌트를 제외한 모든 것을 알파벳 순서로 소개합니다. 구성 요소를 설명하기 위해 "컨테이너" 또는 "컨테이너"를 사용할 때 특별한 의미가 있습니다. 상위 구성 요소에 의해 인스턴스화되고 제어되는 하위 구성 요소를 의미합니다. ComponentManager 또는 ComponentSelector를 통해 얻는 구성 요소를 의미하는 것은 아닙니다. 또한 컨테이너 구성 요소가 수신한 일부 Avalon 단계 실행 명령은 해당 하위 구성 요소가 해당 인터페이스를 구현하는 한 모든 하위 구성 요소에 전파되어야 합니다. 특정 인터페이스에는 초기화 가능, 시작 가능, 일시 중단 가능 및 일회용이 있습니다. 이러한 방식으로 계약을 배열하는 이유는 이러한 인터페이스에 특별한 실행 규칙이 있기 때문입니다. Component
Avalon Framework의 핵심입니다. 이 고려 방향에 따라 정의된 인터페이스는 ComponentException을 발생시킵니다. Component
모든 Avalon 구성 요소는 구성 요소 인터페이스를 구현해야 합니다. Component Manager와 Component Selector는 Component만 처리합니다. 이 인터페이스에는 정의된 메서드가 없습니다. 단지 토큰 인터페이스 역할을 할 뿐입니다. 모든 구성 요소는 매개 변수 없이 기본 생성자를 사용해야 합니다. 모든 구성은 구성 가능 또는 매개변수화 가능 인터페이스를 통해 수행됩니다.
Composable
다른 구성요소를 사용하는 구성요소는 이 인터페이스를 구현해야 합니다. 이 인터페이스에는 ComponentManager 유형의 단일 매개변수를 사용하는 compose() 메소드가 하나만 있습니다. 이 인터페이스를 둘러싼 계약은 compose()가 구성 요소 수명 동안 한 번만 호출된다는 것입니다. 메서드를 정의하는 다른 인터페이스와 마찬가지로 이 인터페이스는 역방향 제어 패턴을 사용합니다. 이는 구성 요소의 컨테이너에 의해 호출되며 구성 요소에 필요한 구성 요소만 ComponentManager에 나타납니다.
재구성 가능
드물지만 구성 요소에 새 ComponentManager와 새 구성 요소-역할 매핑 관계가 필요한 경우가 있습니다. 이러한 경우 재구성 가능한 인터페이스를 구현해야 합니다. 메서드 이름도 recompose()인 Composable의 이름과 다릅니다. 이 인터페이스에 대한 계약은 recompose() 메서드를 여러 번 호출할 수 있지만 구성 요소가 완전히 초기화되기 전에는 호출할 수 없다는 것입니다. 이 메서드가 호출되면 구성 요소는 안전하고 일관된 방식으로 자체 업데이트되어야 합니다. 일반적으로 이는 구성 요소가 수행하는 모든 작업이 업데이트 사이에 중지되고 업데이트 후에 다시 시작되어야 함을 의미합니다.
Activity
이 인터페이스 세트는 구성 요소 수명 주기 계약과 관련이 있습니다. 이 인터페이스 호출 집합 중에 오류가 발생하면 일반 예외가 발생할 수 있습니다. 일회용
구성 요소가 더 이상 필요하지 않다는 것을 구조화된 방식으로 알아야 하는 경우 Disposable 인터페이스를 사용할 수 있습니다. 구성 요소의 할당이 취소되면 더 이상 사용할 수 없습니다. 실제로는 가비지 수집을 기다리고 있는 것뿐입니다. 이 인터페이스에는 매개 변수가 없는 dispose() 메서드가 하나만 있습니다. 이 인터페이스를 둘러싼 계약은 dispose() 메서드가 한 번 호출되고 구성 요소 수명 동안 마지막으로 호출된다는 것입니다. 또한 구성 요소가 더 이상 사용되지 않으며 구성 요소가 점유한 리소스를 해제해야 함을 나타냅니다.
Initialized
구성 요소가 다른 구성 요소를 생성해야 하거나 다른 초기화 단계에서 정보를 얻기 위해 초기화 작업을 수행해야 하는 경우 초기화 가능 인터페이스를 사용해야 합니다. 이 인터페이스에는 매개 변수가 없는 초기화() 메서드가 하나만 있습니다. 이 인터페이스를 둘러싼 계약은 초기화() 메서드가 한 번 호출되고 초기화 프로세스 중에 호출되는 마지막 메서드라는 것입니다. 또한 구성 요소가 활성 상태이고 시스템의 다른 구성 요소에서 사용될 수 있음을 나타냅니다.
Startable
구성 요소가 수명 동안 계속 실행되는 경우 시작 가능 인터페이스를 사용해야 합니다. 이 인터페이스는 start() 및 stop()이라는 두 가지 메서드를 정의합니다. 두 방법 모두 매개변수가 없습니다. 이 인터페이스를 둘러싼 계약은 구성 요소가 완전히 초기화된 후 start() 메서드가 한 번 호출된다는 것입니다. stop() 메서드는 구성 요소가 삭제되기 전에 한 번 호출됩니다. 그 중 어느 것도 두 번 호출되지 않습니다. start()는 항상 stop()보다 먼저 호출됩니다. 이 인터페이스의 구현은 시스템 불안정을 초래하지 않고 start() 및 stop() 메서드를 안전하게(Thread.stop() 메서드와 달리) 실행하는 데 필요합니다.
Suspendable
구성 요소가 수명 동안 자체적으로 일시 중지되도록 허용하는 경우 Suspendable 인터페이스를 사용해야 합니다. 일반적으로 항상 Startable 인터페이스와 함께 사용되지만 필수는 아닙니다. 이 인터페이스에는 현수막()과 재개()라는 두 가지 메서드가 있습니다. 두 방법 모두 매개변수가 없습니다. 이 인터페이스를 둘러싼 계약은 다음과 같습니다. 현수막() 및 재개()는 여러 번 호출할 수 있지만 구성 요소가 초기화되고 시작되기 전이나 구성 요소가 중지되고 삭제된 후에는 호출할 수 없습니다. 일시 중단된 구성 요소에서 suspens() 메서드를 호출하거나 이미 실행 중인 구성 요소에서 이력서()를 호출해도 아무런 효과가 없습니다.
구성
이 인터페이스 세트는 구성 고려 사항을 설명합니다. 필수 Configuration 요소가 없는 등의 문제가 발생하면 ConfigurationException이 발생할 수 있습니다. Configurable
구성에 따라 동작을 결정해야 하는 구성 요소는 구성 개체의 인스턴스를 가져오기 위해 이 인터페이스를 구현해야 합니다. 이 인터페이스에는 구성 유형의 매개변수가 하나만 있는 구성() 메소드가 있습니다. 이 인터페이스를 둘러싼 계약은 구성 요소의 수명 동안 구성() 메서드가 한 번 호출된다는 것입니다. 전달된 구성 개체는 null이 아니어야 합니다.
Configuration
구성 개체는 몇 가지 속성을 갖는 구성 요소로 구성된 트리입니다. 어떤 면에서는 구성 개체를 매우 단순화된 DOM으로 생각할 수 있습니다. 이 글에서 소개하기에는 Configuration 클래스의 메소드가 너무 많습니다. JavaDoc 문서를 참고하세요. 구성 개체에서 String, int, long, float 또는 boolean 값을 가져올 수 있습니다. 구성이 없으면 기본값이 제공됩니다. 속성도 마찬가지입니다. 하위 구성 개체를 얻을 수도 있습니다. 계약에는 값이 있는 구성 개체에 하위 개체가 없어야 하며 하위 개체의 경우에도 마찬가지라고 명시되어 있습니다. 상위 구성 개체를 가져올 수 없다는 것을 알 수 있습니다. 그것이 디자인이 하는 일이다. 시스템 구성의 복잡성을 줄이기 위해 대부분의 경우 컨테이너는 하위 구성 개체를 하위 구성 요소에 전달합니다. 하위 구성요소는 상위 구성 값에 액세스할 수 없어야 합니다. 이 접근 방식은 약간의 불편을 가져올 수 있지만 Avalon 팀은 타협이 필요할 때 항상 보안을 최우선으로 선택합니다.
Reconfigurable
이 인터페이스를 구현하는 구성 요소는 재구성 가능한 구성 요소와 매우 유사하게 동작합니다. reconfigure() 메서드는 하나만 있습니다. 이 디자인 결정은 Re로 시작하는 인터페이스의 학습 난이도를 줄이는 것입니다. Reconfigurable은 Recomposable과 Composable을 구성하는 것입니다.
Context
Avalon의 Context 개념은 컨테이너에서 구성 요소로 간단한 개체를 전달하는 메커니즘의 필요성에서 비롯되었습니다. 정확한 프로토콜과 이름 바인딩은 개발자에게 최대한의 유연성을 제공하기 위해 의도적으로 정의되지 않았습니다. Context 개체 사용과 관련된 계약은 시스템에서 사용자가 정의하지만 메커니즘은 동일합니다. Context
Context 인터페이스는 하나의 get() 메소드만 정의합니다. Object 유형의 매개변수를 가지며 매개변수 객체를 키 값으로 사용하여 객체를 반환합니다. Context 개체는 컨테이너에 의해 조립된 다음 하위 구성 요소에 전달됩니다. 하위 구성 요소는 Context에 대한 읽기 권한만 갖습니다. Context가 하위 구성 요소에 대해 항상 읽기 전용이라는 점을 제외하고는 다른 계약이 없습니다. Avalon의 Context를 확장하는 경우 본 계약을 준수하도록 주의하시기 바랍니다. 이는 역방향 제어 모델의 일부이자 보안 설계의 일부입니다. 또한 컨텍스트가 읽기 전용이어야 하는 것과 같은 이유로 컨텍스트의 컨테이너에 대한 참조를 전달하는 것은 좋지 않습니다.
컨텍스트화 가능
컨테이너로부터 Context 객체를 수신하려는 구성 요소는 이 인터페이스를 구현해야 합니다. 여기에는 contextualize()라는 메서드가 있으며 매개변수는 컨테이너에 의해 조립된 Context 개체입니다. 이 인터페이스를 둘러싼 계약은 구성 요소 수명 동안 LogEnabled 이후, 다른 초기화 메서드 이전에 contextualize()가 한 번 호출된다는 것입니다.
재컨텍스트화 가능
이 인터페이스를 구현하는 구성 요소는 재구성 가능한 구성 요소와 매우 유사하게 동작합니다. 여기에는 recontextualize()라는 메서드가 하나만 있습니다. 이 디자인 결정은 Re로 시작하는 인터페이스의 학습 난이도를 줄이기 위한 것입니다. 재구성 가능이 구성 가능하듯이 재컨텍스트화 가능은 컨텍스트화 가능입니다.
Resolvable
Resolvable 인터페이스는 일부 특정 컨텍스트에서 해결해야 하는 일부 개체를 식별하는 데 사용됩니다. 예를 들어, 객체는 여러 Context 객체에 의해 공유되고 특정 Context에 따라 자체 동작을 변경합니다. 컨텍스트는 객체가 반환되기 전에 해결() 메서드를 호출합니다.
Logger
모든 시스템에는 이벤트를 기록하는 기능이 필요합니다. Avalon은 LogKit 프로젝트를 내부적으로 사용합니다. LogKit에는 Logger 인스턴스에 정적으로 액세스하기 위한 몇 가지 방법이 있지만 프레임워크에서는 역방향 제어 패턴을 사용할 것으로 예상합니다. LogEnabled
Logger 인스턴스가 필요한 모든 구성 요소는 이 인터페이스를 구현해야 합니다. 이 인터페이스에는 Avalon Framework Logger 인스턴스를 구성 요소에 전달하는 활성화Logging()이라는 메서드가 있습니다. 이 인터페이스에 대한 계약은 구성 요소 수명 동안 다른 초기화 단계 전에 한 번만 호출된다는 것입니다.
Logger
Logger 인터페이스는 다양한 로그 라이브러리를 추상화하는 데 사용됩니다. 고유한 클라이언트 API를 제공합니다. Avalon Framework는 이 인터페이스를 구현하는 세 가지 캡슐화 클래스인 LogKit용 LogKitLogger, Log4J용 Log4jLogger 및 JDK1.4 로깅 메커니즘용 Jdk14Logger를 제공합니다.
Parameters
Avalon은 많은 상황에서 구성 개체 계층 구조가 너무 무겁다는 것을 인식합니다. 따라서 우리는 이름-값 쌍 접근 방식을 사용하여 Configuration 개체에 대한 대안을 제공하기 위해 매개 변수 개체를 제안합니다. Parameterizing
구성 개체 대신 매개 변수를 사용하려는 모든 구성 요소는 이 인터페이스를 구현합니다. Parameterized에는 매개변수가 매개변수 객체인 매개변수화()라는 메서드가 하나만 있습니다. 이 인터페이스를 둘러싼 계약은 구성 요소의 수명 동안 한 번 호출된다는 것입니다. 이 인터페이스는 구성 가능 인터페이스와 호환되지 않습니다.
Parameters
Parameters 개체는 문자열 유형 이름을 통해 값을 얻는 메커니즘을 제공합니다. 값이 존재하지 않는 경우 기본값을 사용하거나 Configurable 인터페이스에서 동일한 형식의 값을 가져올 수 있는 편리한 방법이 있습니다. 매개변수 객체와 java.util.Property 객체 사이의 유사성에도 불구하고 중요한 의미상의 차이가 있습니다. 첫째, 매개변수는 읽기 전용입니다. 둘째, 매개변수는 항상 구성 개체에서 쉽게 내보내집니다. 마지막으로, XML 조각에서 매개 변수 개체를 내보내며 다음과 같습니다.
Thread
스레드 마커가 사용됩니다. 구성요소의 사용법에 따른 컨테이너입니다. 스레드 안전성을 고려하고 구성 요소 구현을 위한 태그를 제공합니다. 가장 좋은 방법은 궁극적으로 구성 요소를 구현하는 클래스까지 이러한 인터페이스의 구현을 연기하는 것입니다. 이렇게 하면 구성 요소가 ThreadSafe로 표시되지만 여기에서 파생된 구성 요소 구현이 스레드로부터 안전하지 않은 경우의 복잡함을 피할 수 있습니다. 이 패키지에 정의된 인터페이스는 LifeStyle 인터페이스 제품군이라고 부르는 것의 일부를 구성합니다. 또 다른 LifeStyle 인터페이스는 Excalibur 패키지의 일부입니다(따라서 이 핵심 인터페이스 세트의 확장임). Poolable은 Excalibur의 풀 구현에 정의되어 있습니다. SingleThreaded
SingleThreaded 구성 요소와 관련된 계약은 이 인터페이스를 구현하는 구성 요소가 동시에 여러 스레드에서 액세스할 수 없다는 것입니다. 각 스레드에는 이 구성 요소의 자체 인스턴스가 있어야 합니다. 또 다른 접근 방식은 구성 요소가 요청될 때마다 새 인스턴스를 만드는 대신 구성 요소 풀을 사용하는 것입니다. 풀을 사용하려면 이 인터페이스 대신 Avalon Excalibur의 Poolable 인터페이스를 구현해야 합니다.
ThreadSafe
ThreadSafe 구성 요소와 관련된 계약은 동시에 구성 요소에 액세스하는 스레드 수에 관계없이 해당 인터페이스와 구현이 정상적으로 작동한다는 것입니다. 이는 유연한 설계 목표이지만 사용하는 기술에 따라 때로는 달성할 수 없는 경우도 있습니다. 이 인터페이스를 구현하는 구성 요소는 일반적으로 시스템에 하나의 인스턴스만 가지며 다른 구성 요소는 이 인스턴스를 사용합니다.
Others
Avalon Framework의 루트 패키지에 있는 이러한 클래스와 인터페이스에는 Exception 계층 구조와 일부 공통 유틸리티 클래스가 포함되어 있습니다. 하지만 언급할 가치가 있는 클래스가 하나 있습니다. Version
JavaTM 버전 기술은 jar 패키지의 매니페스트 파일에 명시되어 있습니다. 문제는 jar의 압축을 풀 때 버전 정보가 손실되고 버전 정보가 쉽게 수정되는 텍스트 파일에 저장된다는 것입니다. 이러한 문제를 더 가파른 학습 곡선과 결합하면 구성 요소와 인터페이스의 버전을 확인하기가 어렵습니다. Avalon 개발팀은 버전을 쉽게 확인하고 비교할 수 있도록 Version 개체를 설계했습니다. 구성 요소에 Version 개체를 구현할 수 있으며 적절한 구성 요소 또는 최소 버전 번호를 테스트하는 것이 더 쉽습니다.
꿈을 실현하세요
Avalon Framework와 Avalon Excalibur를 사용하여 서비스 애플리케이션을 구현하는 방법을 보여 드리겠습니다. Avalon을 사용하는 것이 얼마나 쉬운지 보여드리겠습니다.
분석을 완료한 후에는 시스템을 구성하는 구성 요소와 서비스를 생성해야 합니다. Avalon은 여러분이 사용할 수 있는 몇 가지 프로그래밍 습관만 설명한다면 그다지 유용하지 않을 것입니다. 하지만 그렇더라도 이러한 프로그래밍 습관과 패턴을 적용하는 것은 전체 시스템을 이해하는 데 도움이 될 것입니다. Avalon Excalibur는 귀하의 시스템에서 삶을 더 쉽게 만들기 위해 사용할 수 있는 몇 가지 유용한 구성 요소와 도구를 제공합니다. 데모에서는 구성 요소를 정의하고 이를 구현하기 위해 저장소에서 문서를 가져오는 전체 프로세스를 진행합니다. 이론적 비즈니스 서버에 대한 논의를 기억하신다면, 우리는 이 구성 요소를 서비스로 식별했습니다. 실제 상황에서는 컴포넌트가 서비스인 경우가 많습니다.
컴포넌트 구현
여기서 컴포넌트 구현 방법을 정의합니다. 앞서 언급한 DocumentRepository 컴포넌트를 구현하는 전체 과정을 살펴보겠습니다. 가장 먼저 파악해야 할 것은 구성 요소가 어떤 영역에 중점을 두고 있는지입니다. 그런 다음 구성 요소를 만들고 관리하는 방법을 알아내야 합니다. 초점 영역을 선택하세요
앞서 DocumentRepository 구성 요소에 대한 역할과 인터페이스를 정의했으며 구현을 만들 준비가 되었습니다. DocumentRepository 인터페이스는 하나의 메서드만 정의하므로 스레드로부터 안전한 구성 요소를 만들 수 있습니다. 이는 최소한의 리소스 소비만 허용하므로 가장 널리 사용되는 구성 요소 유형입니다. 스레드로부터 안전한 구현을 위해서는 구성 요소를 구현하는 방법에 대해 신중하게 생각해야 합니다. 모든 문서는 데이터베이스에 저장되어 있고 외부 Guardian 구성 요소를 사용하려고 하므로 다른 구성 요소에 액세스해야 합니다. 책임 있는 개발자로서 우리는 구성 요소를 디버깅하고 내부적으로 진행되는 상황을 추적하는 데 도움이 될 수 있는 정보를 기록하고 싶습니다. Avalon 프레임워크의 장점은 필요한 인터페이스만 구현하고 필요하지 않은 인터페이스는 무시할 수 있다는 것입니다. 이것이 우려사항 분리의 이점입니다. 고려해야 할 새로운 측면을 발견하면 관련 인터페이스를 구현하여 구성 요소에 새로운 기능을 추가하기만 하면 됩니다. 구성 요소를 사용하는 부품을 변경할 필요가 없습니다. 스레드 안전성은 설계 목표이므로 ThreadSafe 인터페이스를 구현해야 한다는 것을 이미 알고 있습니다. DocumentRepository 인터페이스에는 메서드가 하나만 있으므로 이 구성 요소의 작업 인터페이스를 사용하면 이 요구 사항을 충족합니다. 그리고 우리는 Component가 완전히 초기화되기 전에는 사용되지 않으며, 소멸된 후에도 사용되지 않는다는 것을 알고 있습니다. 디자인을 완성하려면 몇 가지 암시적 인터페이스를 구현해야 합니다. 우리는 구성 요소가 완전히 초기화되었는지 여부를 명시적으로 알 수 있을 만큼 솔루션이 충분히 안전하길 원합니다. 이 목표를 달성하기 위해 초기화 가능 및 일회용 인터페이스를 구현합니다. 환경에 대한 정보가 변경되거나 사용자 정의가 필요할 수 있으므로 DocumentRepository가 Configurable 인터페이스를 구현하도록 해야 합니다. 필요한 구성 요소의 인스턴스를 얻기 위해 Avalon에서 제공하는 방법은 ComponentManager를 사용하는 것입니다. ComponentManager에서 구성요소 인스턴스를 가져오려면 Composable 인터페이스를 구현해야 합니다. DocumentRepository는 데이터베이스의 문서에 액세스하므로 결정을 내려야 합니다. Avalon Excalibur DataSourceComponent를 사용하시겠습니까, 아니면 데이터베이스 연결 관리 코드를 직접 구현하시겠습니까? 이 기사에서는 DataSourceComponent를 활용하겠습니다. 이 시점에서 우리의 클래스 뼈대는 다음과 같습니다: public class DatabaseDocumentRepositoryextends AbstractLogEnabledimplements DocumentRepository , Configurable, Composable, 초기화 가능, Disposable, Component, ThreadSafe{ private boolean 초기화 = false; private ComponentManager Manager = false; = null; /*** 생성자. 모든 구성 요소는 유효한 구성 요소가 되려면 인수 없는 공용 생성자 *가 필요합니다.*/ public DatabaseDocumentRepository() {} /*** 구성. 구성 요소가 * 이미 구성되어 있는지 확인합니다. 이는 * 한 번만 구성을 호출하는 정책을 시행하기 위해 수행됩니다.*/ public final voidconfigure(Configuration conf) throws ConfigurationException { if(초기화됨 || 삭제됨) { throw new IllegalStateException( "Illegal call"); } if (null == this.dbResource) { this.dbResource = conf.getChild("dbpool").getValue(); getLogger().debug("데이터베이스 풀 사용: " + this.dbResource ); // getLogger()에 주목하세요. // 이는 거의 모든 구성요소에 대해 확장됩니다. } } /*** 구성. 구성 요소가 * 이미 초기화되었거나 삭제되었는지 확인합니다. 이는 적절한 수명주기 관리 정책을 시행하기 위해 수행됩니다.*/ public final void compose(ComponentManager cmanager) throws ComponentException { if (초기화됨 || 처리됨) { throw new IllegalStateException ("불법 호출") } if (null == this.manager) { this.manager = cmanager; } } public final void 초기화() throws Exception { if (null == this.manager) { throw new IllegalStateException("작성되지 않음"); } if (null == this.dbResource) { throw new IllegalStateException("구성되지 않음") } if (disposed) { throw new IllegalStateException("이미 삭제됨"); .initialized = true; } public final void dispose() { this.disposed = true; this.dbResource = null; } public final Document getDocument(주요 요청자, int refId) { if (!initialized || disposed) { throw new IllegalStateException("Illegal call") } // TODO: FILL IN LOGIC }}
위 코드에서 몇 가지 구조적 패턴을 찾을 수 있습니다. 보안을 염두에 두고 디자인할 때는 구성 요소의 각 계약을 명시적으로 적용해야 합니다. 보안은 가장 약한 링크만큼만 강력합니다. 완전히 초기화된 것이 확실한 경우에만 구성 요소를 사용하십시오. 구성 요소가 삭제된 후에는 다시 사용하지 마십시오. 자신의 클래스를 작성할 때 동일한 방식으로 수행할 것이기 때문에 이 논리를 여기에 넣었습니다.
구성 요소 인스턴스화 및 관리 구성 요소
컨테이너/컴포넌트 관계가 어떻게 작동하는지 이해하기 위해 먼저 컴포넌트를 수동으로 관리하는 방법에 대해 논의하겠습니다. 다음으로 Avalon의 Excalibur 구성 요소 아키텍처가 복잡성을 어떻게 숨기는지 논의하겠습니다. 구성 요소를 직접 관리하고 싶은 경우도 있을 것입니다. 그러나 대부분의 경우 엑스칼리버는 귀하의 요구를 충족할 수 있는 성능과 유연성을 갖추고 있습니다. 수동 방식
Avalon의 모든 구성 요소는 어딘가에서 생성됩니다. 구성 요소를 생성하는 코드는 구성 요소의 컨테이너입니다. 컨테이너는 구성요소의 구성부터 폐기까지 수명주기를 관리하는 역할을 담당합니다. 컨테이너에는 명령줄에서 호출할 수 있는 정적 "기본" 메서드가 있거나 다른 컨테이너가 있을 수 있습니다. 컨테이너를 디자인할 때 역방향 제어 패턴을 기억하세요. 정보 및 메서드 호출은 컨테이너에서 구성 요소로만 전달됩니다. Subversion of Control
Subversion of Control은 역제어의 반대 패턴입니다. Subversion 제어는 컨테이너에 대한 참조를 구성 요소에 전달할 때 달성됩니다. 구성 요소가 자체 수명 주기를 관리하도록 하는 경우에도 마찬가지입니다. 이러한 방식으로 작동하는 코드는 결함이 있는 것으로 간주되어야 합니다. 컨테이너/구성 요소 관계를 혼합하면 상호 작용으로 인해 시스템의 보안 디버깅 및 감사가 어려워집니다.
하위 구성 요소를 관리하려면 평생 동안 해당 구성 요소에 대한 참조를 유지해야 합니다. 컨테이너 및 기타 구성 요소가 하위 구성 요소를 사용하려면 먼저 초기화를 완료해야 합니다. DocumentRepository의 경우 코드는 다음과 같습니다. class ContainerComponent Implements Component, 초기화 가능, Disposable{ DocumentRepository docs = new DatabaseDocumentRepository(); GuardianComponent Guard = new DocumentGuardianComponent(); DefaultComponentManager Manager = new DefaultComponentManager() throws 예외 { 로거 docLogger = new LogKitLogger( Hierarchy.defaultHierarchy() .getLoggerFor( "document" ) ); this.docs.enableLogging( docLogger.childLogger( "repository" ) ) this.guard.enableLogging( docLogger .childLogger( "security) " ) ); DefaultConfiguration pool = new DefaultConfiguration("dbpool"); pool.setValue("main-pool"); DefaultConfiguration conf = new DefaultConfiguration(""); conf.addChild(pool); this.manager.addComponent( DocumentRepository .ROLE, this.docs ); this.manager.addComponent( GuardianComponent.ROLE, this.guard ); this.docs.compose( this.manager ); this.docs.configure (conf); this.docs.initialize(); } public void dispose() { this.guard.dispose()
간결하게 하기 위해 위 코드에서 명시적인 검사를 제거했습니다. 수동으로 컴포넌트를 생성하고 관리하는 것이 세부적인 작업임을 알 수 있습니다. 구성 요소 수명 주기의 한 단계를 수행하는 것을 잊어버리면 버그를 발견하게 됩니다. 또한 인스턴스화하는 구성 요소에 대한 깊은 지식이 필요합니다. 또 다른 접근 방식은 위의 ContainerComponent에 몇 가지 메서드를 추가하여 구성 요소의 초기화를 동적으로 처리하는 것입니다.
자동화된 자율성
개발자는 본질적으로 게으르기 때문에 시스템의 모든 구성 요소에 대한 컨테이너 역할을 하는 특별한 ComponentManager를 작성하는 데 시간을 보냅니다. 이렇게 하면 시스템에 있는 모든 구성 요소의 인터페이스를 깊이 이해할 필요가 없습니다. 이는 실망스러운 작업이 될 수 있습니다. Avalon 개발자는 그런 괴물을 만들었습니다. Avalon Excalibur의 구성 요소 아키텍처에는 XML 구성 파일을 통해 제어되는 ComponentManager가 포함되어 있습니다. Excalibur의 ComponentManager에 컴포넌트 관리 책임을 넘겨주면 트레이드오프가 발생합니다. CompomentManager에 포함되는 구성 요소에 대한 세부적인 제어를 포기합니다. 그러나 시스템 규모가 상당히 큰 경우 수동 제어가 실망스러울 수 있습니다. 이 경우 시스템 안정성을 위해서는 시스템의 모든 구성 요소를 한 곳에서 중앙 집중적으로 관리하는 것이 가장 좋습니다. 엑스칼리버의 컴포넌트 아키텍처에는 다양한 통합 수준이 있으므로 가장 낮은 수준부터 시작하겠습니다. Excalibur에는 각 구성 요소 유형에 대한 독립적인 컨테이너 역할을 하는 ComponentHandler 개체 집합이 있습니다. 구성요소의 전체 수명주기를 관리합니다. 라이프스타일 인터페이스의 개념을 소개해보자. 생존 인터페이스는 시스템이 구성 요소를 처리하는 방법을 설명합니다. 구성 요소의 작동 방식이 시스템 작동에 영향을 미치므로 현재 작동 방식의 일부 의미를 논의해야 합니다. · org.apache.avalon.framework.thread.SingleThreadedo는 스레드로부터 안전하지 않거나 재사용이 불가능합니다. o 다른 생존 모드 인터페이스가 지정되지 않은 경우 시스템은 이를 고려합니다. o 구성 요소가 요청될 때마다 새로운 인스턴스가 생성됩니다. o 인스턴스 생성 및 초기화는 구성 요소가 요청될 때까지 연기됩니다. · org.apache.avalon.framework.thread.Threadsafeo 구성 요소는 완전히 재진입 가능하며 모든 스레드 안전 원칙을 준수합니다. o 시스템은 인스턴스를 생성하고 이에 대한 액세스는 모든 컴포저블 구성 요소에서 공유됩니다. o ComponentHandler가 생성되면 인스턴스 생성 및 초기화가 완료됩니다. · org.apache.avalon.excalibur.pool.Poolableo는 스레드로부터 안전하지 않지만 완전히 재사용 가능합니다. o 인스턴스 세트를 생성하고 이를 풀에 넣습니다. 컴포저블 구성 요소가 이를 요청하면 시스템은 사용 가능한 인스턴스를 제공합니다. o ComponentHandler가 생성되면 인스턴스 생성 및 초기화가 완료됩니다. ComponentHandler 인터페이스는 처리가 매우 간단합니다. Java 클래스, Configuration 객체, ComponentManager 객체, Context 객체 및 RoleManager 객체를 통해 생성자를 초기화합니다. 구성 요소에 위 중 하나가 필요하지 않다는 것을 알고 있는 경우 해당 위치에 null을 업로드할 수 있습니다. 그런 다음 구성요소에 대한 참조가 필요할 때 "get" 메소드를 호출합니다. 완료되면 "put" 메서드를 호출하여 구성 요소를 ComponentHandler에 반환합니다. 다음 코드를 사용하면 이를 더 쉽게 이해할 수 있습니다. 클래스 ContainerComponent는 구성 요소, 초기화 가능, 일회용을 구현합니다. ComponentHandler docs = null; DefaultComponentManager 관리자 = new DefaultComponentManager()는 예외를 발생시킵니다. { DefaultConfiguration pool = new DefaultConfiguration("dbpool"); "main-pool"); DefaultConfiguration conf = new DefaultConfiguration(""); this.docs.configure(conf); this.docs = ComponentHandler.getComponentHandler( DatabaseDocumentRepository.class, conf, this.manager , null, null); this.guard = ComponentHandler.getComponentHandler( DocumentGuardianComponent.class, this.manager, null, null); 로거 docLogger = new LogKitLogger( Hierarchy.defaultHierarchy() .getLoggerFor( "document" ) ); .docs.enableLogging( docLogger.childLogger( "repository" ) ); this.guard.enableLogging( docLogger.childLogger( "security" ) ) this.manager.addComponent(DocumentRepository.ROLE, this.docs); addComponent(GuardianComponent.ROLE, this.guard); this.docs.initialize() } public void dispose() { this.docs.dispose(); }}
여기서는 몇 줄의 코드만 놓쳤습니다. 우리는 여전히 Configuration 객체를 수동으로 생성하고 Logger를 설정했으며 ComponentHandler 객체를 초기화하고 삭제해야 했습니다. 여기서 우리가 하는 일은 단지 인터페이스 변경에 의해 영향을 받는 것을 방지하는 것입니다. 이런 식으로 코딩하는 것이 도움이 될 수 있습니다. 엑스칼리버는 한 단계 더 나아간다. 대부분의 복잡한 시스템에는 일부 구성 파일이 있습니다. 이를 통해 관리자는 중요한 구성 정보를 조정할 수 있습니다. Excalibur는 다음 형식의 구성 파일을 읽고 이 파일에서 시스템 구성 요소를 생성할 수 있습니다.
이것은 실제로입니다. 올바른 구성 요소를 수동으로 작성하기 위한 이전 코드는 작업을 단순화하고 프로그래밍할 때 명시적으로 알아야 하는 정보를 제한합니다. 실제로 무언가를 저장했는지 알아보기 위해 Container 클래스를 다시 살펴보겠습니다. 5개의 구성요소(ComponentSelector는 하나의 구성요소로 간주됨)와 각 구성요소에 대한 구성 정보를 지정했음을 기억하십시오. 클래스 ContainerComponent 구현 구성 요소, 초기화 가능, 일회용 { ExcaliburComponentManager 관리자 = 새 ExcaliburComponentManager(); public void 초기화() 예외 발생 { DefaultConfigurationBuilder builder = new DefaultConfigurationBuilder() 구성 sysConfig = builder.buildFromFile("./conf/system.xconf") ; this.manager.setLogger( Hierarchy.getDefaultHierarchy() .getLoggerFor("document") ); this.manager.contextualize( new DefaultContext() ) this.manager.initialize(); } public void dispose() { this.manager.dispose() }}
정말 놀랍지 않나요? 두 배 이상의 코드 양(코드 13줄 대신 6줄)으로 구성 요소 수를 두 배 이상 초기화했습니다. 이 구성 파일에는 단점이 있습니다. 약간 이상해 보이지만 작성해야 하는 코드의 양을 최소화합니다. ExcaliburComponentManager의 뒤에서는 많은 활동이 일어나고 있습니다. 구성 파일의 각 "컴포넌트" 요소에 대해 Excalibur는 각 클래스 항목에 대해 ComponentHandler를 생성하고 역할과 해당 관계를 설정합니다. "구성요소" 요소와 해당 하위 요소는 모두 구성요소에 대한 구성입니다. 컴포넌트가 ExcaliburComponentSelector인 경우, 엑스칼리버는 각 "컴포넌트 인스턴스" 요소를 읽고 이전과 동일한 유형의 작업을 수행하며, 이번에는 힌트 항목과 해당 관계를 설정합니다. 구성 파일을 더 보기 좋게 만드세요
별칭을 사용하여 구성 파일의 모양을 변경할 수 있습니다. Excalibur는 RoleManager를 사용하여 구성 시스템에 대한 별칭을 제공합니다. RoleManager는 특별히 생성한 클래스일 수도 있고, DefaultRoleManager를 사용하여 구성 개체를 전달할 수도 있습니다. DefaultRoleManager를 사용하면 역할 구성 파일과 시스템의 다른 부분을 jar 파일에 숨길 것입니다. 캐릭터 프로필은 개발자만이 변경할 수 있기 때문입니다. 다음은 RoleManager 인터페이스입니다: 인터페이스 RoleManager{ String getRoleForName( String shorthandName ); String getDefaultClassNameForRole( String role ); String getDefaultClassNameForHint( String 힌트, String shorthand );}
Excalibur가 프레임워크에서 RoleManager를 사용하는 방법을 살펴보겠습니다. 먼저 Excalibur는 루트 요소의 모든 하위 요소를 반복합니다. 여기에는 모든 "구성 요소" 요소가 포함되지만 이번에는 Excalibur가 요소 이름을 인식하지 못하고 RoleManager에게 이 구성 요소에 사용할 역할을 묻습니다. RoleManager가 null을 반환하는 경우 해당 요소와 해당 하위 요소는 모두 무시됩니다. 다음으로 Excalibur는 역할 이름에서 클래스 이름을 파생합니다. 마지막 접근 방식은 클래스 이름을 ComponentSelector의 하위 유형에 동적으로 매핑하는 것입니다. Excalibur는 XML 구성 파일을 사용하는 RoleManager의 기본 구현을 제공합니다. 태그는 매우 간단하며 관리자가 보지 않기를 원하는 모든 추가 정보를 숨깁니다. <역할 목록> <역할 이름="org.apache.avalon.excalibur.datasource.DataSourceComponentSelector" shorthand="datasources" default-class="org.apache.avalon.excalibur.comComponent.ExcaliburComponentSelector"< 힌트 shorthand="jdbc" class="org.apache.avalon.excalibur.datasource.JdbcDataSourceComponent"/>
RoleManager를 사용하려면 다음이 필요합니다. 컨테이너 클래스 "초기화" 방법을 변경합니다. 구성 빌더를 사용하여 이 파일에서 구성 트리를 구성합니다. RoleManager를 사용하려는 경우 "configure" 메소드를 호출하기 전에 "setRoleManager" 메소드를 호출해야 한다는 점을 기억하십시오. 클래스 로더에서 이 XML 파일을 가져올 수 있는 방법을 보여주기 위해 아래 기술을 보여 드리겠습니다. DefaultConfigurationBuilder builder = new DefaultConfigurationBuilder();Configuration sysConfig = builder.buildFromFile("./conf/system.xconf");Configuration roleConfig = builder.build( this.getClass().getClassLoader() .getResourceAsStream("/org/apache/bizserver/docs/document.roles"));DefaultRoleManager 역할 = 새로운 DefaultRoleManager();roles.enableLogging(Hierarchy.getDefaultHierarchy () .getLoggerFor("document.roles"));roles.configure(roleConfig);this.manager.setLogger( Hierarchy.getDefaultHierarchy() .getLoggerFor("document") );this.manager.contextualize( new DefaultContext() ); this.manager.setRoleManager(roles);this.manager.configure(sysConfig);this.manager.initialize();
이제 6줄의 코드를 추가했으니 어떤 이점이 있는지 살펴보겠습니다. 최종 구성 파일은 다음과 같이 작성할 수 있습니다:
구성 요소 사용
이제 구성 요소를 만들었으므로 이를 사용하겠습니다. 구성 요소가 초기화되고 관리되는 방식에 관계없이 구성 요소에 액세스하는 방법은 동일합니다. ComponentManager에서 참조를 얻으려면 Composable 인터페이스를 구현해야 합니다. ComponentManager는 필요한 모든 구성 요소에 대한 참조를 보유합니다. 논의의 편의를 위해 우리가 얻는 ComponentManager가 이전 섹션의 최종 구성 파일에 따라 구성되었다고 가정하겠습니다. 이는 리포지토리, Guardian 및 두 개의 데이터 소스가 있음을 의미합니다. 구성 요소 관리 인프라 사용 원칙
구성 요소 관리 인프라를 사용하려면 참조하는 구성 요소를 릴리스해야 합니다. 이렇게 제한하는 이유는 구성 요소 리소스를 적절하게 관리하기 위한 것입니다. ComponentManager는 특정 역할에 대해 다양한 유형의 구성 요소가 있다는 점을 고려하여 설계되었습니다. ComponentSelector의 또 다른 독특한 측면은 구성 요소로도 설계된다는 것입니다. 이를 통해 ComponentManager에서 ComponentSelector를 가져올 수 있습니다. 외부 구성 요소에 대한 참조를 처리하는 두 가지 합법적인 방법이 있습니다. 초기화 중에 참조를 얻고 파기 시 해제할 수 있습니다. try/catch/finally 블록에 구성 요소를 처리하는 코드를 넣을 수도 있습니다. 두 방법 모두 장점과 단점이 있습니다. 초기화 및 폐기 방법
class MyClass는 Component, Composable, Disposable을 구현합니다. ComponentManager Manager; Guardian myGuard; /*** 가드에 대한 참조를 얻고 ComponentManager에 대한 참조를 * 유지합니다.*/ public void compose(ComponentManager Manager) throws ComponentException { if (this.manager == null) { this. 관리자 = 관리자; myGuard = (가디언) this.manager.lookup(Guardian.ROLE) } } /*** Guardian을 이용한 방식입니다.*/ public void myMethod() throws SecurityException { this.myGuard.checkPermission(new BasicPermission("test") )); } /*** 우리의 참조를 제거*/ public void dispose() { this.manager.release(this.myGuard); this.myGuard = null; this.manager = null }}
샘플 코드에서 보세요, 이렇게 하는 것은 쉽습니다. 객체가 처음 ComponentManager를 수신하면 Guardian 구성 요소에 대한 참조를 얻습니다. Guardian 구성 요소가 스레드로부터 안전하다는 것을 확인할 수 있다면(ThreadSafe 인터페이스 구현) 다음 작업만 수행하면 됩니다. 불행히도 장기적으로 이를 보장할 수는 없습니다. 리소스를 올바르게 관리하려면 작업이 완료된 후 구성 요소에 대한 참조를 해제해야 합니다. 이것이 바로 ComponentManager에 대한 참조를 유지하는 이유입니다. 이 접근 방식의 가장 큰 단점은 구성 요소 풀의 구성 요소를 처리할 때입니다. 구성 요소에 대한 참조는 구성 요소를 활성 상태로 유지합니다. 객체의 수명이 짧은 경우에는 문제가 되지 않을 수 있지만 객체가 Excalibur 구성 요소 관리 아키텍처에 의해 관리되는 구성 요소인 경우 해당 객체에 대한 참조가 있는 한 수명이 계속됩니다. 이는 실제로 구성 요소 풀을 구성 요소 공장으로 전환한다는 의미입니다. 이 접근 방식의 주요 이점은 구성 요소를 가져오고 릴리스하는 코드가 명확하다는 것입니다. 예외 처리 코드를 이해할 필요는 없습니다. 또 다른 미묘한 차이점은 Guardian의 존재를 이 개체를 초기화하는 능력과 연결한다는 것입니다. 객체의 초기화 단계에서 예외가 발생하면 해당 객체가 유효한 객체가 아니라고 가정해야 합니다. 필요한 구성요소가 존재하지 않는 경우 프로그램이 실패하기를 원하는 경우도 있지만 이는 문제가 되지 않습니다. 구성요소를 설계할 때 이러한 암시적 의미를 실제로 인식해야 합니다.
예외 처리 접근 방식
class MyClass는 Composable, Disposable을 구현합니다. } /*** 가드에 대한 참조를 얻고 ComponentManager에 대한 참조를 * 유지합니다.*/ public void myMethod() throws SecurityException { Guardian myGuard = null; try { myGuard = (Guardian) this.manager.lookup(Guardian.ROLE) } catch( ComponentException ce) { throw new SecurityException(ce.getMessage()); } catch (SecurityException se) { throw se; } finally { if (myGuard != null) { this.manager.release(myGuard); ** 가디언을 얻는 방법입니다.*/ public void importantSection(Guardian myGuard)가 SecurityException을 발생시킵니다. { myGuard.checkPermission(new BasicPermission("test") }}
보시다시피 이 코드는 약간 복잡합니다. 이를 이해하려면 예외 처리를 이해해야 합니다. 대다수의 Java 개발자는 예외 처리 방법을 알고 있으므로 이는 문제가 되지 않을 수 있습니다. 이렇게 하면 구성 요소가 더 이상 필요하지 않게 되는 즉시 릴리스되므로 구성 요소의 수명에 대해 너무 걱정할 필요가 없습니다. 이 접근 방식의 가장 큰 단점은 더 복잡한 예외 처리 코드가 추가된다는 것입니다. 복잡성을 최소화하고 코드를 더 쉽게 유지 관리하기 위해 작업 코드를 추출하여 다른 방법에 넣습니다. try 블록에서는 구성 요소에 대한 참조를 원하는 만큼 얻을 수 있다는 점을 기억하세요. 이 접근 방식의 주요 이점은 구성 요소 참조를 보다 효율적으로 관리할 수 있다는 것입니다. 마찬가지로 ThreadSafe 구성 요소를 사용하는 경우에는 실제 차이가 없지만 구성 요소 풀의 구성 요소를 사용하는 경우에는 차이가 있습니다. 구성 요소를 사용할 때마다 구성 요소에 대한 새 참조를 가져오려면 약간의 오버헤드가 있지만 새 구성 요소 인스턴스를 강제로 만들어야 할 가능성은 크게 줄어듭니다. 초기화 및 소멸이 작동하는 방식과 마찬가지로 이해해야 할 미묘한 차이가 있습니다. 관리자가 구성 요소를 찾을 수 없는 경우 초기화 중에 프로그램이 실패하지 않는 방식으로 예외 처리가 수행됩니다. 앞서 언급했듯이 이것이 전혀 장점이 없는 것은 아닙니다. 많은 경우 특정 구성 요소가 있을 것으로 예상하지만 예상한 구성 요소가 없다고 해서 프로그램이 실패할 필요는 없습니다.
ComponentSelector에서 구성 요소 가져오기
대부분의 작업에서는 ComponentManager만 사용하면 됩니다. 이제 DataSourceComponent의 여러 인스턴스가 필요하다고 결정했으므로 원하는 인스턴스를 얻는 방법을 알아야 합니다. ComponentSelector는 처리 중에 원하는 참조를 얻기 위한 힌트가 있기 때문에 ComponentManager보다 조금 더 복잡합니다. 이미 명확하게 설명한 것처럼 구성 요소는 특정 역할에 속합니다. 그러나 때로는 캐릭터의 여러 구성 요소 중에서 하나를 선택해야 하는 경우도 있습니다. ComponentSelector는 임의의 개체를 힌트로 사용합니다. 대부분의 경우 이 객체는 문자열이지만 올바르게 국제화된 구성 요소를 얻기 위해 Locale 객체를 사용할 수도 있습니다. 우리가 설정한 시스템에서는 DataSourceComponent의 올바른 인스턴스를 선택하기 위해 문자열을 사용하도록 선택했습니다. 올바른 구성 요소를 얻기 위해 필요한 문자열을 지정하기 위해 구성 요소도 제공했습니다. 이는 시스템 관리를 더 쉽게 만들어주기 때문에 따라하는 것이 좋습니다. 이렇게 하면 시스템 관리자가 이러한 마법의 구성 값을 기억할 필요 없이 다른 구성 요소에 대한 참조를 더 쉽게 볼 수 있습니다. 개념적으로 ComponentSelector에서 구성 요소를 가져오는 것과 ComponentManager에서 구성 요소를 가져오는 것 사이에는 차이가 없습니다. 이제 한 단계만 더 하면 됩니다. ComponentSelector도 구성 요소라는 점을 기억하세요. ComponentSelect의 역할을 조회하면 ComponentManager가 ComponentSelector 구성 요소를 준비하여 사용자에게 반환합니다. 그런 다음 이를 통해 구성 요소를 선택해야 합니다. 이를 설명하기 위해 앞서 설명한 예외 처리 코드를 확장하겠습니다. public void myMethod()는 예외를 발생시킵니다{ ComponentSelector dbSelector = null; DataSourceComponent datasource = null; try { dbSelector = (ComponentSelector) this.manager.lookup(DataSourceComponent.ROLE + "Selector"); .useDb); this.process(datasource.getConnection()); } catch (Exception e) { throw e; } finally { if (datasource != null) { dbSelector.release(datasource) } if (dbSelector != null ) { this.manager.release(dbSelector); } }}
지정된 컴포넌트의 역할을 사용하여 ComponentSelector에 대한 참조를 얻은 것을 볼 수 있습니다. 앞서 언급한 역할 명명 규칙에 따라 역할 이름에 "Selector"를 접미사로 추가했습니다. 정적 인터페이스를 사용하여 시스템의 모든 역할 이름을 처리하면 코드에서 문자열 연결 수를 줄일 수 있습니다. 이것도 완벽하게 받아들여집니다. 다음으로 ComponentSelector에서 DataSource 구성 요소에 대한 참조를 가져와야 합니다. 예제 코드에서는 구성 개체에서 필요한 정보를 가져와 "useDb"라는 클래스 변수에 배치했다고 가정합니다.
Excalibur의 도구
마지막 섹션에서는 Apache Avalon Excalibur에서 제공하는 여러 유형의 구성 요소와 도구를 소개합니다. 이러한 도구 클래스는 강력하며 실제 생산 시스템에서 사용할 수 있습니다. 우리는 잠재적인 새로운 도구 클래스의 구현 세부 사항을 작업하는 "Scratchpad"라는 비공식 계층적 프로젝트를 가지고 있습니다. Scratchpad의 도구는 품질이 다양하며 유용할 수는 있지만 동일한 방식으로 사용된다는 보장은 없습니다. 명령줄 인터페이스(CLI)
CLI 도구 클래스는 Avalon Phoenix 및 Apache Cocoon을 포함한 일부 프로젝트에서 명령줄 매개변수를 처리하는 데 사용됩니다. 도움말 정보를 인쇄하기 위한 메커니즘을 제공하고 짧은 이름이나 긴 이름의 형태로 매개변수 옵션을 처리할 수 있습니다.
수집 도구 수업
컬렉션 유틸리티 클래스는 JavaTM 컬렉션 API에 몇 가지 향상된 기능을 제공합니다. 이러한 향상된 기능에는 두 목록 사이의 교차점을 찾는 기능과 PriorityQueue가 포함됩니다. 이는 간단한 선입후출 스택에서 객체 우선순위 변경을 구현할 수 있는 스택의 향상된 기능입니다.
구성요소 관리
우리는 앞서 이 측면의 사용법에 대해 논의했습니다. 엑스칼리버에서 가장 복잡한 몬스터이지만 몇 가지 클래스만으로 많은 기능을 제공합니다. 단순한 SingleThreaded 또는 ThreadSafe 관리 유형 외에도 Poolable 유형도 있습니다. 컴포넌트가 SingleThreaded 인터페이스 대신 Excalibur의 Poolable 인터페이스를 구현하는 경우 컴포넌트 풀을 유지하고 인스턴스를 재사용합니다. 대부분의 경우 이것은 잘 작동합니다. 일부 구성 요소를 재사용할 수 없는 경우 SingleThreaded 인터페이스가 사용됩니다.
LogKit Management
Avalon 개발 팀은 복잡한 로그 대상 계층을 생성하려면 많은 사람들에게 간단한 메커니즘이 필요하다는 것을 깨달았습니다. RoleManager와 유사한 아이디어를 바탕으로 팀은 앞서 언급한 Excalibur Component Management 시스템에서 사용할 수 있는 LogKitManager를 개발했습니다. "로거" 속성을 기반으로 다양한 구성요소에 해당하는 로거 개체를 제공합니다.
스레딩 도구 클래스
concurrent 패키지는 다중 스레드 프로그래밍을 지원하는 몇 가지 클래스를 제공합니다: Lock(뮤텍스 구현), DjikstraSemaphore, ConditionalEvent 및 ThreadBarrier
Datasources
이것은 javax.sql.DataSource 클래스를 기반으로 설계되었지만 단순화되었습니다. DataSourceComponent에는 두 가지 구현이 있습니다. 하나는 명시적으로 JDBC 연결 풀을 사용하는 것이고 다른 하나는 J2EE 애플리케이션 서버의 javax.sql.DataSource 클래스를 사용하는 것입니다.
입/출력(IO) 유틸리티 클래스
IO 유틸리티 클래스는 일부 FileFilter 클래스와 파일 및 IO 관련 유틸리티 클래스를 제공합니다.
Pool 구현
Pool은 다양한 상황에서 사용할 수 있는 Pool을 구현합니다. 구현 중 하나는 매우 빠르지만 하나의 스레드에서만 사용할 수 있습니다. FlyWeight 모드를 구현하는 것이 좋습니다. 풀의 개체 수를 관리하지 않는 DefaultPool도 있습니다. SoftResourceManagingPool은 개체가 반환될 때 임계값을 초과하는지 여부를 확인합니다. 임계값을 초과하면 개체가 "폐기"됩니다. 마지막으로 최대 개체 수에 도달하면 HardResourceManagingPool에서 예외가 발생합니다. 후자의 세 풀은 모두 ThreadSafe입니다.
Property 유틸리티 클래스
Property 유틸리티 클래스는 Context 객체와 함께 사용됩니다. 이를 통해 Resolvable 객체에서 "변수"를 확장할 수 있습니다. 작동 방식은 다음과 같습니다. "${resource}"는 "resource"라는 컨텍스트에서 값을 찾고 기호를 해당 값으로 바꿉니다.
결론
Avalon은 오랜 시간의 테스트를 거쳐 여러분이 사용할 준비가 되었습니다. 이 섹션에 제공된 증거는 확립된 프레임워크를 사용하는 것이 스스로 프레임워크를 만드는 것보다 낫다는 것을 자신과 다른 사람들에게 확신시키는 데 도움이 될 수 있습니다.
이미 확신하고 계시겠지만 동료들에게 Avalon이 올바른 선택임을 설득하는 데 도움이 필요합니다. 어쩌면 당신도 자신을 설득해야 할 수도 있습니다. 그럼에도 불구하고, 이 장은 당신의 생각을 정리하고 설득력 있는 증거를 제공하는 데 도움이 될 것입니다. 우리는 오픈 소스 모델에 대한 두려움, 불확실성, 의심(FUD)에 맞서 싸워야 합니다. 오픈 소스의 효율성에 대한 증거와 관련하여 N400017이라는 주제에 대한 Eric S. Raymond의 뛰어난 논평을 읽어 보시기 바랍니다. 그의 견해가 무엇이든 그의 기사는 The Cathedral and the Bazaar Information이라는 책으로 정리되어 홍보용으로 제공될 것입니다. 일반적으로 오픈 소스를 수용합니다.
Avalon은 작동합니다
우리의 결론은 Avalon이 원래 달성하도록 설계된 것을 달성한다는 것입니다. Avalon은 새로운 개념과 아이디어를 도입하는 대신 오랜 시간에 걸쳐 검증된 개념을 사용하고 이를 표준화합니다. Avalon 디자인에 영향을 미치는 최신 개념은 1995년경에 제안된 우려 사항 분리 패턴입니다. 그때에도 고려 사항의 분리는 시스템 분석 기술에 대한 공식화된 접근 방식이었습니다. Avalon의 사용자 기반은 수백 명에 이릅니다. Apache Cocoon, Apache JAMES, Jesktop과 같은 일부 프로젝트는 Avalon을 기반으로 구축되었습니다. 이 프로젝트의 개발자는 Avalon Framework의 사용자입니다. Avalon은 사용자 수가 매우 많기 때문에 잘 테스트되었습니다. 최고에 의해 설계됨
Avalon의 저자는 우리가 서버 측 컴퓨팅 분야의 유일한 전문가 그룹이 아니라는 점을 인식하고 있습니다. 우리는 다른 사람들의 연구에서 얻은 개념과 아이디어를 사용했습니다. 우리는 사용자의 피드백에 응답합니다. Avalon은 앞서 언급한 5명의 개발자가 단순히 역제어, 분리 고려 사항 및 구성 요소 지향 프로그래밍에 대한 아이디어를 가져와 설계한 것이 아닙니다. 오픈소스의 가장 큰 장점은 최고의 아이디어와 최고의 코드가 융합되어 결과가 나온다는 것입니다. Avalon은 아이디어를 테스트하고 더 나은 솔루션이 있다는 이유로 일부를 거부하는 단계를 거쳤습니다. Avalon 개발팀에서 얻은 지식을 활용하여 자신의 시스템에 적용할 수 있습니다. Excalibur의 사전 정의된 구성 요소를 프로젝트에 사용할 수 있으며, 과부하 상태에서도 오류 없이 실행되도록 테스트되었습니다.
호환성을 위한 라이선스
ASL(Apache 소프트웨어 라이선스)은 알려진 다른 라이선스와 호환됩니다. 알려진 가장 큰 예외는 GNU Public License(GPL)와 Lesser GNU Public License(LGPL)입니다. 중요한 점은 ASL이 공동 개발에 매우 우호적이며 원하지 않는 경우 소스 코드를 공개하도록 강요하지 않는다는 것입니다. 에게. Apache Software Foundation의 높은 평가를 받는 HTTP 서버는 동일한 라이선스에 따라 라이선스가 부여됩니다.
클러스터 기반 R&D
대부분의 Avalon 사용자는 어떤 방식으로든 기여합니다. 이로 인해 개발, 디버깅 및 문서 작업량이 여러 사용자에게 분산됩니다. 이는 또한 Avalon의 코드가 기업 개발에서 가능한 것보다 더 광범위한 동료 검토를 거쳤음을 보여줍니다. 또한 Avalon 사용자는 Avalon을 지원합니다. 오픈 소스 프로젝트에는 일반적으로 핫머니에 대한 헬프 데스크나 전화 지원이 없지만 메일링 리스트는 있습니다. 많은 질문에 대해 일부 지원 핫라인보다 메일링 리스트를 통해 신속하게 답변할 수 있습니다.
간소화된 분석 및 설계
Avalon을 기반으로 개발하면 개발자가 정신 상태를 달성하는 데 도움이 됩니다. 이러한 마음 상태에서 개발자의 작업은 구성 요소와 서비스를 검색하는 방법에 중점을 둡니다. 이제 구성 요소와 서비스의 수명에 대한 세부 사항이 분석되고 설계되었으므로 개발자는 필요한 것을 선택하기만 하면 됩니다. Avalon은 전통적인 객체지향 분석과 설계를 대체하는 것에서 시작된 것이 아니라 이를 강화함으로써 시작되었다는 점을 지적하는 것이 중요합니다. 이전에 사용했던 것과 동일한 기술을 계속 사용하고 있지만 이제는 디자인을 더 빠르게 구현할 수 있는 도구 세트가 있습니다.
Avalon이 준비되었습니다
Avalon Framework, Avalon Excalibur 및 Avalon LogKit을 사용할 준비가 되었습니다. 그들은 성숙했고 점점 나아지고 있습니다. Avalon Phoenix 및 Avalon Cornerstone은 집중적으로 개발 중이지만 이를 기반으로 작성하는 코드는 향후 약간의 변경만으로 작동할 것입니다.
위 내용은 Avalonjs에 대한 자세한 소개의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!