Prism은 어떻게 모듈을 관리하는가
코드는 여기를 참조
https://github.com/PrismLibrary/Prism-Samples-Wpf
여기서 07- Modules-LoadManual
나는 이 코드를 실행 하고 싶어도 실행이 안된다. 이유가 뭔지는 모름 ㅠㅠ
https://github.com/2Bbear/WindowsProgrmaDevelop/tree/master/WPF/UsingMvvmPrismLibrary/HowToPrismModules
일단 성공한 프로젝트
아무튼 분석을 하자면.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | public partial class App : PrismApplication { protected override Window CreateShell() { return Container.Resolve<MainWindow>(); } protected override void RegisterTypes(IContainerRegistry containerRegistry) { } protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog) { var moduleAType = typeof(ModuleAModule); moduleCatalog.AddModule(new ModuleInfo() { ModuleName = moduleAType.Name, ModuleType = moduleAType.AssemblyQualifiedName, InitializationMode = InitializationMode.OnDemand }); } } | cs |
app에서 Mainwindow를 시작 윈도우로 설정하였다.
추가적으로 CinfigureModuleCatalog메소드가 보이는데 이 메소드는 응용프로그램 시작 후 Booststrapper에서 실행하게 되는 메소드이다.
내용을 보자면 자동적으로 만들어진 moduleCatalog를 매개변수로 받아
ModuleModule이라는 괴상한 이름의 클래스 타입을 모듈로 추가하게된다.
이때 추가하는 형태가 ModuleInfo라는 형태인데 ModuleInfo 안에는 ModuleName, Moduletype, InitializationMode 등을 설정할 수 있게 되어 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | public partial class MainWindow : Window { IModuleManager _moduleManager; public MainWindow(IModuleManager moduleManager) { InitializeComponent(); _moduleManager = moduleManager; } private void Button_Click(object sender, RoutedEventArgs e) { _moduleManager.LoadModule("ModuleAModule"); } } | cs |
Mainwindow으로 가게되면 moduleManager를 받아오고
클릭할 경우 모듈 메니저에서 LoadModule이라는 메소드를 실행시킨다. 이때 넣는 매개변수가 모듈 이름이 된다
그럼 도대체 이 모듈모듈ModuleModule은 어디서 가저오는 것일까?
ModulA라는 네임스페이스 안에 ModuleModue이 존재하고있다.
이런 변태 같은.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | using ModuleA.Views; using Prism.Ioc; using Prism.Modularity; using Prism.Regions; namespace ModuleA { public class ModuleAModule : IModule { public void OnInitialized(IContainerProvider containerProvider) { var regionManager = containerProvider.Resolve<IRegionManager>(); regionManager.RegisterViewWithRegion("ContentRegion", typeof(ViewA)); } public void RegisterTypes(IContainerRegistry containerRegistry) { } } } | cs |
IModule을 상속받아 구현된 OnInitialized는 매개변수로 컨테이너 제공자라는 매개변수를 받아오는데, 이 컨테이너제공자는 해당 모듈을 실행시킬 때 모듈을 실행 시키는 주체 모듈로부터 받아와진다.
따라서 받은 컨테이너 제공자에서 IRegionManager를 호출하면 해당 RegionManager안에서 ContentRegion이라는 등록명에 ModuleModule의 ViewA를 등록하면
연결이 완료된다.
'중단한 프로젝트 > WPF_PrismLibrary(추후진행)' 카테고리의 다른 글
Prism에서 CustomViewModel을 view에 붙이는 방법 (0) | 2019.02.19 |
---|---|
Prism으로 어떻게 다른 모듈의 view를 호출하는가 (0) | 2019.02.19 |
Prism은 어떻게 View를 다른 View로 변경하는가 (0) | 2019.02.19 |
Prism은 어떻게 View상호작용 view를 호출하는가 (0) | 2019.02.19 |
Prism은 어떻게 View에서 View를 호출하는가 - ViewDiscovery (0) | 2019.02.19 |
Prism은 어떻게 View를 다른 View로 변경하는가
전 포스팅에서 다뤘듯이 RegionManager를 통해 등록명으로 접근 view를 출력 시킬 수 있다는 것을 알 수 있었다.
그러면 응용으로 런타임시에 view를 바꿀 수 있지 않을까?
https://github.com/2Bbear/WindowsProgrmaDevelop/tree/master/WPF/UsingMvvmPrismLibrary/06-ViewActivationDeactivation
코드 참조는 이곳에서 하면 된다
1 2 3 4 5 6 7 8 9 10 11 12 | public partial class App : PrismApplication { protected override Window CreateShell() { return Container.Resolve<MainWindow>(); } protected override void RegisterTypes(IContainerRegistry containerRegistry) { } } | cs |
app에서 Mainwindow를 실행시키게 해놓았다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | public partial class MainWindow : Window { IContainerExtension _container; IRegionManager _regionManager; IRegion _region; ViewA _viewA; ViewB _viewB; public MainWindow(IContainerExtension container, IRegionManager regionManager) { InitializeComponent(); _container = container; _regionManager = regionManager; this.Loaded += MainWindow_Loaded; } private void MainWindow_Loaded(object sender, RoutedEventArgs e) { _viewA = _container.Resolve<ViewA>(); _viewB = _container.Resolve<ViewB>(); _region = _regionManager.Regions["ContentRegion"]; _region.Add(_viewA); _region.Add(_viewB); } private void Button_Click(object sender, RoutedEventArgs e) { //activate view a _region.Activate(_viewA); } private void Button_Click_1(object sender, RoutedEventArgs e) { //deactivate view a _region.Deactivate(_viewA); } private void Button_Click_2(object sender, RoutedEventArgs e) { //activate view b _region.Activate(_viewB); } private void Button_Click_3(object sender, RoutedEventArgs e) { //deactivate view b _region.Deactivate(_viewB); } } | cs |
Mainwindow를 확인하면
생성자로 받을 컨테이너와 리전매니저를 저장할 멤버변수가 보이고
Loaded 이벤트에 Mainwindow_Loaded 메소드가 붙어있는 것을 확인 할 수 있다.
Loaded 이벤트는 윈도우가 로드 될때 실행되는 이벤트로 그러한 의미에서
MainWindow_Loaded는 윈도우가 실행될때 실행되게 되는 메소드다. 왜 굳이 loaded에 놓았을까?...
Loaded 메소드는 생성자의 InitializeComponent에서 Loaded를 실행시킨다.. 아니면 누가 알주세요.
따라서 InitialzeComponent가 된 후에 Loaded를 한다는 것은....xaml이 실행되기 전에 load를 연결시키겠다는 의미로 보인다.
이후 Mainwindow_Loaded 메소드를 확인하면 배주에 regionManager에서 region을 호출하여 _region에 담는다.
그렇기에 구조가
_regionManager
-> region["ContentRegion"] = _region
-> _viewA
-> _viewB
이런 식으로 구조가 만들어지게 된다.
_region을 포인터용으로 사용하여 만들어진 region["ContentRegion"] 에 접근하여 view를 추가하는 행위이다.
두개 이상의 view가 하나의 region에 연결되어 있는 경우 기본적으로 제일먼저 붙은 view가 실행되겠지만
region.Activate로 출현할 view를 선택할 수 있다.
아무래도 구조가
_regionManger
->region["ContentRegion"] = _region
->_viewA , true
->_viewB , false
이런 형태로 되어 있는 것 같고,
_region.Activate(view)를 이용하여 해당 region["ContentRegion"] 에서 value값으로 view와 같은 녀석을 검색하고
찾았다면 해당 view의 bool 값을 true로 바꾸고 나머지를 false로 바꾸어 출력 가능 대상을 바꾸는 것 같다.
'중단한 프로젝트 > WPF_PrismLibrary(추후진행)' 카테고리의 다른 글
Prism으로 어떻게 다른 모듈의 view를 호출하는가 (0) | 2019.02.19 |
---|---|
Prism은 어떻게 모듈을 관리하는가 (0) | 2019.02.19 |
Prism은 어떻게 View상호작용 view를 호출하는가 (0) | 2019.02.19 |
Prism은 어떻게 View에서 View를 호출하는가 - ViewDiscovery (0) | 2019.02.19 |
Region이란 무엇인가 (0) | 2019.02.19 |
Prism은 어떻게 View상호작용 view를 호출하는가
기본적인 WPF의 경우
window에서 다른 usercontrol을 출현시키기 위해서는 해당 usercontrol을 알고 있어야 했다.
그것을 prism에서 어떻게 분리하였는지 확인해본다.
https://github.com/2Bbear/WindowsProgrmaDevelop/tree/master/WPF/UsingMvvmPrismLibrary/05-ViewInjection
참고는 이곳에서 하면 된다
1 2 3 4 5 6 7 8 9 10 11 12 | public partial class App : PrismApplication { protected override Window CreateShell() { return Container.Resolve<MainWindow>(); } protected override void RegisterTypes(IContainerRegistry containerRegistry) { } } | cs |
app에서 시작 윈도우로 MainWindow를 지정해 놓았다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | public partial class MainWindow : Window { IContainerExtension _container; IRegionManager _regionManager; public MainWindow(IContainerExtension container, IRegionManager regionManager) { InitializeComponent(); _container = container; _regionManager = regionManager; } /* 어차피 regionmanager에 등록하는 과정이다. 바로 registerviewwithregion으로 하나 분리하여 등록 명 따로 붙일 view따로 하나 하는 행동은 같은 것이다. */ private void Button_Click(object sender, RoutedEventArgs e) { var view = _container.Resolve<ViewA>(); _regionManager.RegisterViewWithRegion("ContentRegion", typeof(ViewA)); //IRegion region = _regionManager.Regions["ContentRegion"]; //region.Add(view); } } | cs |
Main윈도우를 확인하면 IContainerExtension 과 IregionManager가 멤버 변수로 되어 있는데 이는 생성자에서 받을 수 있는 현재 retion manager와 container를 저장하고 있기 위함이다.
이후 Button_Click메소드가
1 2 3 4 5 6 7 8 9 10 11 | <Window x:Class="ViewInjection.Views.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:prism="http://prismlibrary.com/" Title="Shell" Height="350" Width="525"> <DockPanel LastChildFill="True"> <Button DockPanel.Dock="Top" Click="Button_Click">Add View</Button> <ContentControl prism:RegionManager.RegionName="ContentRegion" /> </DockPanel> </Window> | cs |
mainwindow xaml에서 이렇게 붙어있게된다.
이후 컴파일시에는 ContentRegion이라는 것이 무엇인지 몰라 출력하지 않고 있다가
런타임에서 버튼 클릭시 해당 view를 regionmanager에 등록시키는 과정을 진행하여 런타임으로 붙을 수 있게 한다.
'중단한 프로젝트 > WPF_PrismLibrary(추후진행)' 카테고리의 다른 글
Prism은 어떻게 모듈을 관리하는가 (0) | 2019.02.19 |
---|---|
Prism은 어떻게 View를 다른 View로 변경하는가 (0) | 2019.02.19 |
Prism은 어떻게 View에서 View를 호출하는가 - ViewDiscovery (0) | 2019.02.19 |
Region이란 무엇인가 (0) | 2019.02.19 |
Prism의 구성 Bootstrapper 와 Shell (0) | 2019.02.19 |
Prism은 어떻게 View에서 View를 호출하는가 - ViewDiscovery
이 내용은 ViewDiscovery에 대한 내용이다.
https://github.com/2Bbear/WindowsProgrmaDevelop/tree/master/WPF/UsingMvvmPrismLibrary/04-ViewDiscovery
참조는 04번 항목을 참조하면 된다.
1 2 3 4 5 6 7 8 9 10 11 12 | public partial class App : PrismApplication { protected override Window CreateShell() { return Container.Resolve<MainWindow>(); } protected override void RegisterTypes(IContainerRegistry containerRegistry) { } } | cs |
app 부분을 보면 시작 화면으로 MainWindow로 해놓았다.
1 2 3 4 5 6 7 8 9 | public partial class MainWindow : Window { public MainWindow(IRegionManager regionManager) { InitializeComponent(); //view discovery regionManager.RegisterViewWithRegion("ContentRegion", typeof(ViewA)); regionManager.RegisterViewWithRegion("ContentRegion_Main",typeof(MyView)); } } | cs |
MainWindow 생성자를 확인하면 매개변수로 IRegionManager를 받고있다.
이 RegionManager로 모든 View를 관리 할 수 있게 된다.
여기서 regionManger는 응용프로그램 생성 초기 단계에서 만들어진 객체이다.
이제 이 regionManger에 View를 등록해야한다. (등록 이름, 등록할 타입view)
이후
xaml에서는 이 등록 이름으로 각 view를 호출 할 수 있게 된다.
1 2 3 4 5 6 7 8 9 10 11 | <Window x:Class="ViewDiscovery.Views.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:prism="http://prismlibrary.com/" Title="Shell" Height="350" Width="525"> <Grid> <ContentControl prism:RegionManager.RegionName="ContentRegion" /> <ContentControl prism:RegionManager.RegionName="ContentRegion_Main" /> </Grid> </Window> | cs |
이런 식으로 만들어 View가 직접적으로 View를 참조하지 못하게만들어 느슨하게 개발 할 수 있게되는 것이다... 호오.
'중단한 프로젝트 > WPF_PrismLibrary(추후진행)' 카테고리의 다른 글
Prism은 어떻게 모듈을 관리하는가 (0) | 2019.02.19 |
---|---|
Prism은 어떻게 View를 다른 View로 변경하는가 (0) | 2019.02.19 |
Prism은 어떻게 View상호작용 view를 호출하는가 (0) | 2019.02.19 |
Region이란 무엇인가 (0) | 2019.02.19 |
Prism의 구성 Bootstrapper 와 Shell (0) | 2019.02.19 |
Region이란 무엇인가
Region의 생성 순서
https://github.com/2Bbear/WindowsProgrmaDevelop/tree/master/WPF/UsingMvvmPrismLibrary/03-CustomRegions
참조 코드
App RegisterTypes -> App ConfigureRegionAdapterMappings -> StackPanelIRegionAdapter 생성자-> ...
전편의 부트스트랩 생성과정.
두 사진을 비교해서 각 과정을 살펴보면
응용프로그램이 생성되면서 리전이 이미 만들어져있음을 확인 할 수 있다.
'중단한 프로젝트 > WPF_PrismLibrary(추후진행)' 카테고리의 다른 글
Prism은 어떻게 모듈을 관리하는가 (0) | 2019.02.19 |
---|---|
Prism은 어떻게 View를 다른 View로 변경하는가 (0) | 2019.02.19 |
Prism은 어떻게 View상호작용 view를 호출하는가 (0) | 2019.02.19 |
Prism은 어떻게 View에서 View를 호출하는가 - ViewDiscovery (0) | 2019.02.19 |
Prism의 구성 Bootstrapper 와 Shell (0) | 2019.02.19 |
Prism의 구성 Bootstrapper 와 Shell
프리즘의 기본적인 구조
왼편이 개발자가 구현해야하는 부분이고 오른쪽은 Prism이 제공해 놓은 기능이다.
사실 이 구조를 기억한다고 해서 도움이 될 것 같진 않다.
Prism의 모든 이해의 시작은 시작점인 Bootstrapper에서 부터 시작되어야 한다.
1. Bootstrapper
공식 부트스트래퍼 소개내용 : 부트스트래퍼는 Prism 라이브러리를 사용하여 구축된 응용프로그램의 "초기화" 를 담당하고 있는 클래스이다.
즉 Prism으로 만들어진 응용프로그램에서는 Bootstrapper로만 Prism의 기능을 초기화 할때 안전하게 초기화 할 수 있다는 것을 알 수 있다.
부트스트래핑 절차의 기본 단계.
실제로 부트스트래퍼가 실행되는 과정을 로그로 추적하면 이렇게 나온다.
해당 소스코드 : https://github.com/2Bbear/WindowsProgrmaDevelop/tree/master/WPF/UsingMvvmPrismLibrary/PrismLibraryStudyBootStrap
App OnStartUp-> MyBootstrapper createshell -> MainWindow 생성자 -> MyBootstrapper InitializeShell -> App OnStartUp종료
이렇듯. 기존 WPF의 스타트업 구조 사이에 부트스트랩이 끼어듬으로써 MainWindow를 시작하고 있음을 확인 할 수 있다.
2. Shell
특히 Bootstrapper를 상속받은 MyBootstrapper에는 CreateShell이라는 메소드를 오버라이드하고 있는데 이곳에서 return형으로 컨테이너형태에 담고 있는 객체의 형태로 MainWindow의 형태를 갖고 있다.
즉 시작 윈도우를 설정하고 있는 것이다.
기존의 WPF에서는 Uniform Resource Identifiuer라는 URI로 주 화면을 실행하는 코드가 App.xaml에 있었는데 그 코드가 사라지고 부트스트래퍼 CreateShell로 넘어온 것이다.
공식 홈페이지 영어 해석:
Prism 라이브러리로 만든 응용프로그램에서 쉘과 주 화면을 만드는 것은 부트스트래퍼의 책임이다.
쉘은 쉘이 표시되기 전에 등록되어야 하는데 Region Manager같은 서비스에 의존하고 있다.
본문 해석에서 확인 할 수 있듯이 CreateShell이라는 것은 Shell을 만들고 그것을 어딘가에 등록하고 있다. 그 대상에 바로 Region Manager라는 곳이다.
1번 단락의 로그 사진을 다시 살펴보면
App 시작 -> 모듈카테고리 생성 -> 모듈카테고리 환경설정 -> 유니티 컨테이너 생성 -> 유니티 컨테이너 환경설정
-> 컨테이너에 부트스트래퍼 확장 붙이기 -> 서비스로케이션 싱글톤 환경설정 -> 유니티에 사용된 컨테이너 로케이터 환경설정
-> region 어뎁터 환경설정-> 기본 region 행동 환경설정 -> 프레임워크 예외형태 등록
-> Shell 생성 -> Bootstrapper CreateShell 실행 -> MainWindow생성자 실행 -> Region Manager 세팅 -> Region 업데이트
-> Shell 초기화 -> Bootstrapper InitializeShell 실행 -> 모듈 초기화 -> Bootstrapper 시퀸스(흐름) 종료
이런식으로 실행이 되는데 Create Shell을 실행하면서 Region Manager를 호출하는 모습을 확인 할 수 있다.
즉 Shell이 생성되기 이전에 Region Manager가 존재함을 알 수 있다.
여기서 Region Manager에 Shell 붙어있는지 장담을 할 수는 없는데. 아직 코드를 보지 못하였기 때문이다.
아무튼 이렇게 Bootstrapper를 이용하여 응용프로그램을 초기화 하면서 고려해야 할 것들이
- DI 컨테이너를 위해서 MEF, Unity 혹은 또다른 컨테이너를 사용할지 결정해야 한다. 이것은 당신이 사용할 부트스래퍼가 어떤 것이며 당신이 다른 컨테이너에 대해 부트스트래퍼 작성여부를 결정하게 될 것이다.
- 응용프로그램에서 당신이 원하는 응용프로그램의 특정 서비스에 대해 고려해 보아야한다. 즉 고려한 특정 서비스를 컨테이너에 등록해야 하기 때문이다.
- 내장 로깅 서비스가 당신에게 필요한지, 아니면 다른 로깅 서비스를 이용 해야 할지 결정 할 수 있다.
- 응용프로그램이 어떻게 모듈을 탐색할지 결정할 수 있다.
3. Custom Bootstrapper 만들기
https://github.com/PrismLibrary/Prism-Samples-Wpf/tree/master/01-BootstrapperShell
기본적인 예제는 이곳을 참조하는 것이 좋다.
내부적인 구성은
https://pirtaja.tistory.com/entry/Prism-2-Initializing-Applications-Using-the-Prism-Library-50-for-WPF
이곳을 참조하는 것이 좋다.
'중단한 프로젝트 > WPF_PrismLibrary(추후진행)' 카테고리의 다른 글
Prism은 어떻게 모듈을 관리하는가 (0) | 2019.02.19 |
---|---|
Prism은 어떻게 View를 다른 View로 변경하는가 (0) | 2019.02.19 |
Prism은 어떻게 View상호작용 view를 호출하는가 (0) | 2019.02.19 |
Prism은 어떻게 View에서 View를 호출하는가 - ViewDiscovery (0) | 2019.02.19 |
Region이란 무엇인가 (0) | 2019.02.19 |
모듈을 추가하는 방법 (C++ 코드 폴더를 생성하는 방법)
추가 :
Error - 이런 식으로 Custom Module을 만들 경우 Hot reload가 되지 않는 문제점이 발견되었다. Ue4 Ver 21
//
내가 원하는 것은 UE4 에디터 상에서 정리 가능한 C++ 코드 폴더를 만들어 내는 것이다.
이 컨텐츠 브라우저 Classes 안에 새로운 폴더를 만들고 싶은 것이다.
그렇게 하기 위해서는 모듈이라는 것을 만들어야 한다. 모듈에 대한 자세한 설명은 생략한다. 플러그인과 헷갈리지는 말자.
모듈 제작의 규칙
1. 모듈의 이름과 동일한 폴더
2. 모듈의 빌드 규칙 파일 : 모듈이름.Build.cs 로 만들어져 있어야한다.
3. 프리컴파일드 헤더와 소스파일 : 모듈이름.h , 모듈이름.cpp 로 만들어져야 한다.
모듈 제작 순서
1. 폴더를 만들어준다.
내부에 모듈 이름에 맞는 Build.cs 와 cpp, h를 만들어준다.
1-1 cpp , h, build파일에 코드를 작성한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | // Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. using UnrealBuildTool; using System.Collections.Generic; public class TankGameEditorTarget : TargetRules { public TankGameEditorTarget(TargetInfo Target) : base(Target) { Type = TargetType.Editor; //ExtraModuleNames.Add("TankGame"); ExtraModuleNames.AddRange(new string[] { "TankGame", "TankCodes" }); } } | cs |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | // Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. using UnrealBuildTool; using System.Collections.Generic; public class TankGameTarget : TargetRules { public TankGameTarget(TargetInfo Target) : base(Target) { Type = TargetType.Game; //ExtraModuleNames.Add("TankGame"); ExtraModuleNames.AddRange(new string[] { "TankGame","TankCodes"}); } } | cs |
2. 프로젝트 target파일과 프로젝트 Editor.target.cs 파일 두곳에 모듈을 추가해 준다.
보통 프로젝트 폴더 soruce 폴더 하위에 있을 것이다....버전마다 달라질 수 있을 것 같긴한데.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | //WebServerGameTank.Target.cs // Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. using UnrealBuildTool; using System.Collections.Generic; public class WebServerGameTankTarget : TargetRules { public WebServerGameTankTarget(TargetInfo Target) : base(Target) { Type = TargetType.Game; ExtraModuleNames.Add("WebServerGameTank"); ExtraModuleNames.Add("TankCodes"); } } | cs |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | //WebServerGameTankEditor.Target.cs // Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. using UnrealBuildTool; using System.Collections.Generic; public class WebServerGameTankEditorTarget : TargetRules { public WebServerGameTankEditorTarget(TargetInfo Target) : base(Target) { Type = TargetType.Editor; ExtraModuleNames.Add("WebServerGameTank"); ExtraModuleNames.Add("TankCodes"); } } | cs d |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | // Fill out your copyright notice in the Description page of Project Settings. using UnrealBuildTool; public class TankCodes : ModuleRules { public TankCodes(ReadOnlyTargetRules Target) : base(Target) { PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine" }); PrivateDependencyModuleNames.AddRange(new string[] { }); } } | cs |
클래스 이름과 생성자 이름이 바뀐다는 것에 주의하자.
3. uproject 파일을 메모장을 열어 Name과 Type을 추가한다.
연결프로그램으로 메모장을 이용하여 수정하면 되고, 이 코드를 보니 Json인거 같은데.
4. 비쥬얼 스튜디오에서 솔루션 빌드 후 에 uproject파일을 우클릭하여 Generate Visual Studio project files를 실행한다.
비쥬얼 스튜디오에 폴더가 추가되어 있음을 확인 할 수 있다.
5. 에디터에 모듈 폴더를 추가하기 위해서, 에디터를 실행시켜 더미 클래스를 만들어준다.
여전히 추가가 안되어 있는 상태
신기하게 모듈 선택지에 새로 추가한 모듈 이름이 보인다. 해당 이름을 선택한 뒤에 클래스를 생성하면, 5:5 확률로 실패하거나 성공할 수 있는데 이 결과에 굳이 연연하지 말자, 우리가 필요한 것은 단순히 ui로 보이는 코드가 작성되는 것이다.
만약 여기서 None class로 생성한 경우 안될 수도 있으니 AActor를 선택하여 dummy를 만들어 주자.
6. 에디터를 닫는다. 비쥬얼 스튜디오를 다시 킨 뒤 다시 솔루션 빌드(rebuild solution)를 실행시킨다.
이때는 솔루션 빌드가 성공해야한다.
7. 다시 에디터를 켜서 모듈이 추가된 것을 확인한다.
'Engine > Unreal' 카테고리의 다른 글
[Unreal 개발기] 6 - C++과 블루프린트를 이용한 쉽고 간편한 개발 방법 (0) | 2020.02.27 |
---|---|
Plugin을 추가하는 방법 (0) | 2019.03.10 |
[Unreal 개발기] 5 - FVector API 정리 (0) | 2017.02.22 |
[Unreal 개발기] 4 - 클래스간 커뮤니케이션 (0) | 2017.02.22 |
[Unreal 개발기] 3 - 월드에 오브젝트 출현시키는 코드 (0) | 2017.02.21 |
9. 서버 어플리케이션 서비스에 DBConnectionPool을 만들자
이 포스팅은 오라클 DB, 자바 스프링을 이용합니다.
============================================================
왜 DBConnectionPool을 사용해야 하는가
참고 : https://all-record.tistory.com/104
단일 DBConnection을 이용하여 DB 연결을 시도할 경우 동시 다량의 Request 요청에 응답 하지 못할 수가 있다.
따라서 일정한 간격으로 응답하거나 안정적인 응답을 해주기 위하여 DBCP를 만들게 되는데.
가장 큰 요점은 미리 Connection객체를 만들어 놔서 매번 만들어 쓰지 말고 돌려쓰자는 개념이다. 따라서 최소 Connection 객체 수와 최대 Connection 객체 수가 정해져야하는데 이 범위는 각 프로그램의 성능과 하드웨어적 요인과 더불어 프로그래머가 적절히 선택해야 한다고 생각한다.
1. 아파치 Commons dbcp2를 다운 받는다.
참고 : https://www.baeldung.com/java-connection-pooling
이후 다운 받은 jar을 이클립스 프로젝트 라이브러리에 추가시킨다.
2. 코드 작성
HomeController
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 | package com.company.jjh; import static org.junit.Assert.assertTrue; import java.io.IOException; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.text.DateFormat; import java.util.Date; import java.util.Locale; import java.util.Vector; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import com.company.db.DBConnection; import com.company.db.BasicConnectionPool; import com.company.db.ConnectionPool; import com.company.jjh.*; /** * Handles requests for the application home page. */ @Controller public class HomeController { private ConnectionPool connectionPool=null; // //byte로 처리 private int callCount=0; // private static final Logger logger = LoggerFactory.getLogger(HomeController.class); HomeController(){ try { connectionPool=BasicConnectionPool.create("jdbc:oracle:thin:@localhost:1521:orcl", "scott", "0000"); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } @RequestMapping(value = "/", method = RequestMethod.GET) public void home(HttpServletRequest request,HttpServletResponse response) throws IOException { System.out.println("test : 누가 root에 접속했습니다"); response.getWriter().append("test Served at: ").append(request.getContextPath()); System.out.println(response); } @RequestMapping(value = "/Login", method = RequestMethod.GET) public void Process_LogIn(HttpServletRequest request,HttpServletResponse response) throws IOException { System.out.println("test: 누가 login에 접속했습니다"); response.getWriter().append("test Served at Login: "); } @RequestMapping(value = "/TestDB/getEmpTable", method = RequestMethod.GET) public void Process_TestDBGetEmpTable(HttpServletRequest request,HttpServletResponse response) throws IOException { System.out.println("test: DB test GetEmpTalbe이 실행되었습니다"); response.getWriter().append("call count="+callCount+" test Served at TestDBGetEmpTable: "); for(String t : CallDBConnectionPool() ) { response.getWriter().append("<p>"+t+"</p>"); } callCount++; } @RequestMapping(value = "/TestDB/getEmpTable2", method = RequestMethod.GET) public void Process_TestDBGetEmpTable2(HttpServletRequest request,HttpServletResponse response) throws IOException { //System.out.println("test: DB test GetEmpTalbe이 실행되었습니다"); response.getWriter().append(" test Served at TestDBGetEmpTable: "); for(String t : CallDBConnectionPoolNotPrint() ) { response.getWriter().append("<p>"+t+"</p>"); } } @RequestMapping(value = "/TestAppService/getValue", method = RequestMethod.GET) public void Process_TestAppServiceGetValue(HttpServletRequest request,HttpServletResponse response) throws IOException { System.out.println("test: TestAppServiceGetValue이 실행되었습니다"); response.getWriter().append("call count="+callCount+" test Served at TestDBGetEmpTable: "); callCount++; } Vector<String> CallDBConnectionPoolNotPrint() { Connection conn = null; // DB연결된 상태(세션)을 담은 객체 PreparedStatement pstm = null; // SQL 문을 나타내는 객체 ResultSet rs = null; // 쿼리문을 날린것에 대한 반환값을 담을 객체 Vector<String> function_result=new Vector<String>(); try { // SQL 문장을 만들고 만약 문장이 질의어(SELECT문)라면 // 그 결과를 담을 ResulSet 객체를 준비한 후 실행시킨다. String quary = "SELECT * FROM EMP"; conn =connectionPool.getConnection(); pstm = conn.prepareStatement(quary); rs = pstm.executeQuery(); /* EMP 테이블의 데이터 타입 * EMPNO NOT NULL NUMBER(4) -- int ENAME VARCHAR2(10) -- String JOB VARCHAR2(9) -- String MGR NUMBER(4) -- int HIREDATE DATE -- Date SAL NUMBER(7,2) -- float/double COMM NUMBER(7,2) -- float/double DEPTNO NUMBER(2) -- int */ //System.out.println("EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO"); //System.out.println("============================================"); while(rs.next()){ int empno = rs.getInt(1); //int empno = rs.getInt("empno"); 숫자 대신 컬럼 이름을 적어도 된다. String ename = rs.getString(2); String job = rs.getString(3); int mgr = rs.getInt(4); java.sql.Date hiredate = rs.getDate(5); // Date 타입 처리 int sal = rs.getInt(6); int comm = rs.getInt(7); int deptno = rs.getInt(8); String result = empno+" "+ename+" "+job+" "+mgr+" "+hiredate+" "+sal+" "+comm+" "+deptno; //System.out.println(result); function_result.add(result); } } catch (SQLException sqle) { System.out.println("SELECT문에서 예외 발생"); sqle.printStackTrace(); }finally{ // DB 연결을 종료한다. try{ if ( rs != null ){rs.close();} if ( pstm != null ){pstm.close();} if ( conn != null ){connectionPool.releaseConnection(conn); } }catch(Exception e){ throw new RuntimeException(e.getMessage()); } } return function_result; } Vector<String> CallDBConnectionPool() { Connection conn = null; // DB연결된 상태(세션)을 담은 객체 PreparedStatement pstm = null; // SQL 문을 나타내는 객체 ResultSet rs = null; // 쿼리문을 날린것에 대한 반환값을 담을 객체 Vector<String> function_result=new Vector<String>(); try { // SQL 문장을 만들고 만약 문장이 질의어(SELECT문)라면 // 그 결과를 담을 ResulSet 객체를 준비한 후 실행시킨다. String quary = "SELECT * FROM EMP"; conn =connectionPool.getConnection(); pstm = conn.prepareStatement(quary); rs = pstm.executeQuery(); /* EMP 테이블의 데이터 타입 * EMPNO NOT NULL NUMBER(4) -- int ENAME VARCHAR2(10) -- String JOB VARCHAR2(9) -- String MGR NUMBER(4) -- int HIREDATE DATE -- Date SAL NUMBER(7,2) -- float/double COMM NUMBER(7,2) -- float/double DEPTNO NUMBER(2) -- int */ System.out.println("EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO"); System.out.println("============================================"); while(rs.next()){ int empno = rs.getInt(1); //int empno = rs.getInt("empno"); 숫자 대신 컬럼 이름을 적어도 된다. String ename = rs.getString(2); String job = rs.getString(3); int mgr = rs.getInt(4); java.sql.Date hiredate = rs.getDate(5); // Date 타입 처리 int sal = rs.getInt(6); int comm = rs.getInt(7); int deptno = rs.getInt(8); String result = empno+" "+ename+" "+job+" "+mgr+" "+hiredate+" "+sal+" "+comm+" "+deptno; System.out.println(result); function_result.add(result); } } catch (SQLException sqle) { System.out.println("SELECT문에서 예외 발생"); sqle.printStackTrace(); }finally{ // DB 연결을 종료한다. try{ if ( rs != null ){rs.close();} if ( pstm != null ){pstm.close();} if ( conn != null ){connectionPool.releaseConnection(conn); } }catch(Exception e){ throw new RuntimeException(e.getMessage()); } } return function_result; } Vector<String> CallDBConnection() { Connection conn = null; // DB연결된 상태(세션)을 담은 객체 PreparedStatement pstm = null; // SQL 문을 나타내는 객체 ResultSet rs = null; // 쿼리문을 날린것에 대한 반환값을 담을 객체 Vector<String> function_result=new Vector<String>(); try { // SQL 문장을 만들고 만약 문장이 질의어(SELECT문)라면 // 그 결과를 담을 ResulSet 객체를 준비한 후 실행시킨다. String quary = "SELECT * FROM EMP"; conn = DBConnection.getConnection(); pstm = conn.prepareStatement(quary); rs = pstm.executeQuery(); /* EMP 테이블의 데이터 타입 * EMPNO NOT NULL NUMBER(4) -- int ENAME VARCHAR2(10) -- String JOB VARCHAR2(9) -- String MGR NUMBER(4) -- int HIREDATE DATE -- Date SAL NUMBER(7,2) -- float/double COMM NUMBER(7,2) -- float/double DEPTNO NUMBER(2) -- int */ System.out.println("EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO"); System.out.println("============================================"); while(rs.next()){ int empno = rs.getInt(1); //int empno = rs.getInt("empno"); 숫자 대신 컬럼 이름을 적어도 된다. String ename = rs.getString(2); String job = rs.getString(3); int mgr = rs.getInt(4); java.sql.Date hiredate = rs.getDate(5); // Date 타입 처리 int sal = rs.getInt(6); int comm = rs.getInt(7); int deptno = rs.getInt(8); String result = empno+" "+ename+" "+job+" "+mgr+" "+hiredate+" "+sal+" "+comm+" "+deptno; System.out.println(result); function_result.add(result); } } catch (SQLException sqle) { System.out.println("SELECT문에서 예외 발생"); sqle.printStackTrace(); }finally{ // DB 연결을 종료한다. try{ if ( rs != null ){rs.close();} if ( pstm != null ){pstm.close();} if ( conn != null ){conn.close(); } }catch(Exception e){ throw new RuntimeException(e.getMessage()); } } return function_result; } } | cs |
ConnectionPool interface 작성
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | package com.company.db; import java.sql.Connection; import java.sql.SQLException; import java.util.List; public interface ConnectionPool { Connection getConnection() throws SQLException; boolean releaseConnection(Connection connection); String getUrl(); String getUser(); String getPassword(); void shutdown() throws SQLException; List<Connection> getConnectionPool(); } | cs |
BasicConnectionPool 클래스 작성
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 | package com.company.db; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; public class BasicConnectionPool implements ConnectionPool { private final String url; private final String user; private final String password; private final List<Connection> connectionPool; private final List<Connection> usedConnections = new ArrayList<>(); private static final int INITIAL_POOL_SIZE = 10; private final int MAX_POOL_SIZE = 50; public static BasicConnectionPool create(String url, String user, String password) throws SQLException { List<Connection> pool = new ArrayList<>(INITIAL_POOL_SIZE); for (int i = 0; i < INITIAL_POOL_SIZE; i++) { pool.add(createConnection(url, user, password)); } return new BasicConnectionPool(url, user, password, pool); } private BasicConnectionPool(String url, String user, String password, List<Connection> connectionPool) { this.url = url; this.user = user; this.password = password; this.connectionPool = connectionPool; } @Override public Connection getConnection() throws SQLException { if (connectionPool.isEmpty()) { if (usedConnections.size() < MAX_POOL_SIZE) { connectionPool.add(createConnection(url, user, password)); } else { throw new RuntimeException("Maximum pool size reached, no available connections!"); } } Connection connection = connectionPool.remove(connectionPool.size() - 1); usedConnections.add(connection); return connection; } @Override public boolean releaseConnection(Connection connection) { connectionPool.add(connection); return usedConnections.remove(connection); } private static Connection createConnection(String url, String user, String password) throws SQLException { return DriverManager.getConnection(url, user, password); } public int getSize() { return connectionPool.size() + usedConnections.size(); } public List<Connection> getConnectionPool() { return connectionPool; } @Override public String getUrl() { return url; } @Override public String getUser() { return user; } @Override public String getPassword() { return password; } public void shutdown() throws SQLException { usedConnections.forEach(this::releaseConnection); for (Connection c : connectionPool) { c.close(); } connectionPool.clear(); } } | cs |
3. 코드 설명
해당 코드는 jdk 1.8에서 작동하며 프로젝트가 jdk버전에 맞지 않으면 오류가 발생 할 수 있다.
DBCP는 BasicConnectionPool 로서, 해당 객체를 이용하여 Connection 객체들을 관리한다.
때문에 HomeController는 해당 BasicConnectionPool을 이용하여 DB에 접속한다.
특이하게 static으로 만들어진 이 DBCP 클래스는 생성조차 범상치 않다...
connectionPool=BasicConnectionPool.create("jdbc:oracle:thin:@localhost:1521:orcl", "scott", "0000");
이런 식으로 만들어지는데. 나는 처음 보는 문법이므로 설명을 할 수가 없다.
다만 이해를 하자면 static으로 만들어진 블럭을 생성하는 문법으로 람다를 이용 한 것 처럼 보인다고 생각 할 뿐이다. 그러므로 매번 저 create를 사용 할 경우 원하는 db 연결로 변경 해 줄 수 있다고 생각한다.
커넥션 할당은 이렇게 하였고
conn =connectionPool.getConnection();
커넥셜 풀링은
if ( conn != null ){connectionPool.releaseConnection(conn); }
이렇게 해주었다. conn에 무언가 있을 경우 DBCP의 release 메소드를 호출하여 conn값을 넘기면 conn의 객체를 릴리즈 시켜준다.
===========================================================
이후는 단순히 성능 테스트 이므로 보지 않아도 무관하다.
1000회 DB 읽기 작업을 request 하여 걸린 시간
1회 초당 서버 어플리케이션 서비스에 있는 변수를 읽어들인 횟수
2회 초당 서버 어플리케이션 서비스에 있는 변수를 읽어들인 횟수
단순한 C++ 코드로 반복적인 성능 테스트를 한 결과
1. 일반 Connection 보다는 ConnectionPool이 안정적이고 속도는 2배 차이가 난다. 운영체제 HW에 따라 달라진다고 예상된다.
2. ConnectionPool의 max값은 일정 값 이상이 될 경우 속도의 차가 없으며 min값도 일정 값 이상이 될 경우 속도의 차가 없다.
3. 화면 출력 code를 제거시 속도 향상을 보인다.
outstream을 이용하지 않아 일어나는 현상이라 생각한다.
추가 적으로 DB에 접근하지 않고 즉 DBCP를 이용하지 않고 그냥 어플리케이션의 request 성능을 테스트 한결과
1. 초당 100~130 정도의 request 수행 능력을 보여주었다.HW와 OS에 따라 달라진다고 생각한다.
2. 역시 outstream을 이용하지 않을 경우 2초 이상 수행 시간이 단축되는 것을 확인 할 수 있었다.
'중단한 프로젝트 > WebServerGameProject(Team)' 카테고리의 다른 글
8. 스프링 웹 어플리케이션 서비스에 request하면 db에 접속하는 기능을 만들자 (0) | 2019.01.30 |
---|---|
7. rest SDK를 이용하여 request해서 respon 된 값 출력하기 (0) | 2019.01.29 |
6. cpprest , rest 라이브러리로 C++로 http 통신 구하기 (0) | 2019.01.29 |
5. Boost asio를 이용한 네트워크 통신 만들어보기 (0) | 2019.01.28 |
4. c++로 webservice에 연결하는 방법 (0) | 2019.01.24 |