Sama seperti tupai, hewan kecil , lincah , cerdas , waspada , dan lucu , tupai-yayasan bertujuan untuk menyediakan implementasi mesin negara Java yang ringan , sangat fleksibel dan dapat diperluas , dapat didiagnosis , mudah digunakan , dan jenis yang aman untuk penggunaan perusahaan.
Berikut adalah diagram mesin keadaan yang menggambarkan perubahan keadaan ATM:
Kode contoh dapat ditemukan di paket "org.squirrelframework.foundation.fsm.atm" .
tupai-yayasan telah diterapkan ke repositori pusat maven, jadi Anda hanya perlu menambahkan ketergantungan berikut ke pom.xml.
Versi Rilis Terbaru:
<dependency>
<groupId>org.squirrelframework</groupId>
<artifactId>squirrel-foundation</artifactId>
<version>0.3.10</version>
</dependency>
Versi Cuplikan Terbaru:
<dependency>
<groupId>org.squirrelframework</groupId>
<artifactId>squirrel-foundation</artifactId>
<version>0.3.11-SNAPSHOT</version>
</dependency>
Untuk mencoba fungsi mesin keadaan tupai dengan cepat, buatlah proyek pakar dan sertakan ketergantungan pondasi tupai dengan benar. Kemudian jalankan saja kode contoh berikut.
public class QuickStartSample {
// 1. Define State Machine Event
enum FSMEvent {
ToA , ToB , ToC , ToD
}
// 2. Define State Machine Class
@ StateMachineParameters ( stateType = String . class , eventType = FSMEvent . class , contextType = Integer . class )
static class StateMachineSample extends AbstractUntypedStateMachine {
protected void fromAToB ( String from , String to , FSMEvent event , Integer context ) {
System . out . println ( "Transition from '" + from + "' to '" + to + "' on event '" + event +
"' with context '" + context + "'." );
}
protected void ontoB ( String from , String to , FSMEvent event , Integer context ) {
System . out . println ( "Entry State ' " + to + " ' ." );
}
}
public static void main ( String [] args ) {
// 3. Build State Transitions
UntypedStateMachineBuilder builder = StateMachineBuilderFactory . create ( StateMachineSample . class );
builder . externalTransition (). from ( "A" ). to ( "B" ). on ( FSMEvent . ToB ). callMethod ( "fromAToB" );
builder . onEntry ( "B" ). callMethod ( "ontoB" );
// 4. Use State Machine
UntypedStateMachine fsm = builder . newStateMachine ( "A" );
fsm . fire ( FSMEvent . ToB , 10 );
System . out . println ( "Current state is " + fsm . getCurrentState ());
}
}
Saat ini Anda mungkin memiliki banyak pertanyaan tentang kode contoh, harap bersabar. Panduan pengguna berikut akan menjawab sebagian besar pertanyaan Anda. Namun sebelum masuk ke detailnya, Anda harus memiliki pemahaman dasar tentang konsep mesin negara. Materi ini bagus untuk memahami konsep mesin negara. [diagram-mesin-negara] [mesin-negara-qt]
tupai-yayasan mendukung API yang lancar dan cara deklaratif untuk mendeklarasikan mesin negara, dan juga memungkinkan pengguna untuk menentukan metode tindakan dengan cara yang mudah.
Antarmuka StateMachine mengambil empat parameter tipe generik.
Pembangun Mesin Negara
Untuk membuat mesin negara, pengguna harus membuat pembuat mesin negara terlebih dahulu. Misalnya:
StateMachineBuilder < MyStateMachine , MyState , MyEvent , MyContext > builder =
StateMachineBuilderFactory . create ( MyStateMachine . class , MyState . class , MyEvent . class , MyContext . class );
Pembuat mesin negara mengambil parameter yang merupakan jenis mesin negara (T), negara bagian (S), peristiwa (E) dan konteks (C).
API yang Lancar
Setelah pembuat mesin status dibuat, kita dapat menggunakan API yang lancar untuk menentukan status/transisi/tindakan mesin status.
builder . externalTransition (). from ( MyState . A ). to ( MyState . B ). on ( MyEvent . GoToB );
Transisi eksternal dibangun antara status 'A' ke status 'B' dan dipicu pada peristiwa 'GoToB' yang diterima.
builder . internalTransition ( TransitionPriority . HIGH ). within ( MyState . A ). on ( MyEvent . WithinA ). perform ( myAction );
Transisi internal dengan prioritas yang ditetapkan ke tinggi dibangun di dalam status 'A' pada acara 'DalamA' melakukan 'myAction'. Transisi internal berarti setelah transisi selesai, tidak ada keadaan yang keluar atau masuk. Prioritas transisi digunakan untuk mengesampingkan transisi asli ketika mesin negara diperluas.
builder . externalTransition (). from ( MyState . C ). to ( MyState . D ). on ( MyEvent . GoToD ). when (
new Condition < MyContext >() {
@ Override
public boolean isSatisfied ( MyContext context ) {
return context != null && context . getValue ()> 80 ;
}
@ Override
public String name () {
return "MyCondition" ;
}
}). callMethod ( "myInternalTransitionCall" );
Transisi bersyarat dibangun dari status 'C' ke status 'D' pada acara 'GoToD' ketika konteks eksternal memenuhi batasan kondisi, lalu panggil metode tindakan "myInternalTransitionCall". Pengguna juga dapat menggunakan MVEL (bahasa ekspresi yang kuat) untuk menggambarkan kondisi dengan cara berikut.
builder . externalTransition (). from ( MyState . C ). to ( MyState . D ). on ( MyEvent . GoToD ). whenMvel (
"MyCondition:::(context!=null && context.getValue()>80)" ). callMethod ( "myInternalTransitionCall" );
Catatan: Karakter ':::' digunakan untuk memisahkan nama kondisi dan ekspresi kondisi. 'Konteks' adalah titik variabel yang telah ditentukan sebelumnya ke objek Konteks saat ini.
builder . onEntry ( MyState . A ). perform ( Lists . newArrayList ( action1 , action2 ))
Daftar tindakan entri negara ditentukan dalam kode contoh di atas.
Tindakan Panggilan Metode
Pengguna dapat menentukan tindakan anonim selama menentukan transisi atau masuk/keluar negara. Namun, kode tindakan akan tersebar di banyak tempat sehingga membuat kode sulit dipelihara. Selain itu, pengguna lain tidak dapat mengesampingkan tindakan tersebut. Jadi tupai-yayasan juga mendukung untuk menentukan tindakan pemanggilan metode mesin negara yang datang bersama dengan kelas mesin negara itu sendiri.
StateMachineBuilder <...> builder = StateMachineBuilderFactory . create (
MyStateMachine . class , MyState . class , MyEvent . class , MyContext . class );
builder . externalTransition (). from ( A ). to ( B ). on ( toB ). callMethod ( "fromAToB" );
// All transition action method stays with state machine class
public class MyStateMachine <...> extends AbstractStateMachine <...> {
protected void fromAToB ( MyState from , MyState to , MyEvent event , MyContext context ) {
// this method will be called during transition from "A" to "B" on event "toB"
// the action method parameters types and order should match
...
}
}
Selain itu, tupai-yayasan juga mendukung tindakan pemanggilan metode yang ditentukan dengan cara Konvensi Atas Konfigurasi . Pada dasarnya, ini berarti bahwa jika metode yang dideklarasikan di mesin negara memenuhi konvensi penamaan dan parameter, metode tersebut akan ditambahkan ke daftar tindakan transisi dan juga dipanggil pada fase tertentu. misalnya
protected void transitFromAToBOnGoToB ( MyState from , MyState to , MyEvent event , MyContext context )
Metode bernama transitFrom[SourceStateName]To[TargetStateName]On[EventName] , dan diberi parameter sebagai [MyState, MyState, MyEvent, MyContext] akan ditambahkan ke daftar tindakan transisi "A-(GoToB)->B". Saat transit dari status 'A' ke status 'B' pada acara 'GoToB', metode ini akan dipanggil.
protected void transitFromAnyToBOnGoToB ( MyState from , MyState to , MyEvent event , MyContext context )
transitFromAnyTo[TargetStateName]On[EventName] Metode ini akan dipanggil ketika transit dari negara bagian mana pun ke negara bagian 'B' pada acara 'GoToB'.
protected void exitA ( MyState from , MyState to , MyEvent event , MyContext context )
exit[StateName] Metode ini akan dipanggil ketika keluar dari status 'A'. Jadi sebagai entry[StateName] , beforeExitAny / afterExitAny dan beforeEntryAny / afterEntryAny .
Pola Penamaan yang Didukung Lainnya:
transitFrom[fromStateName]To[toStateName]On[eventName]When[conditionName]
transitFrom[fromStateName]To[toStateName]On[eventName]
transitFromAnyTo[toStateName]On[eventName]
transitFrom[fromStateName]ToAnyOn[eventName]
transitFrom[fromStateName]To[toStateName]
on[eventName]
Konvensi metode yang tercantum di atas juga menyediakan fungsionalitas mirip AOP , yang menyediakan kemampuan ekstensi fleksibel bawaan untuk mesin keadaan tupai pada tingkat granularitas apa pun. Untuk informasi lebih lanjut, lihat kasus uji " org.squirrelframework.foundation.fsm.ExtensionMethodCallTest ". Sejak 0.3.1, ada cara lain untuk mendefinisikan metode ekstensi mirip AOP ini yaitu melalui API yang lancar (terima kasih saran dari vittali), misalnya
// since 0.3.1
// the same effect as add method transitFromAnyToCOnToC in your state machine
builder . transit (). fromAny (). to ( "C" ). on ( "ToC" ). callMethod ( "fromAnyToC" );
// the same effect as add method transitFromBToAnyOnToC in your state machine
builder . transit (). from ( "B" ). toAny (). on ( "ToC" ). callMethod ( "fromBToAny" );
// the same effect as add method transitFromBToAny in your state machine
builder . transit (). from ( "B" ). toAny (). onAny (). callMethod ( "fromBToAny" );
Atau melalui anotasi deklaratif, misalnya
// since 0.3.1
@ Transitions ({
@ Transit ( from = "B" , to = "E" , on = "*" , callMethod = "fromBToEOnAny" ),
@ Transit ( from = "*" , to = "E" , on = "ToE" , callMethod = "fromAnyToEOnToE" )
})
Catatan : Metode tindakan ini akan dilampirkan pada transisi yang cocok dan sudah ada, tetapi tidak untuk membuat transisi baru. Sejak 0.3.4, beberapa transisi juga dapat ditentukan satu per satu menggunakan API berikut, misalnya
// transitions(A->B@A2B=>a2b, A->C@A2C=>a2c, A->D@A2D) will be defined at once
builder . transitions (). from ( State . _A ). toAmong ( State . B , State . C , State . D ).
onEach ( Event . A2B , Event . A2C , Event . A2D ). callMethod ( "a2b|a2c|_" );
// transitions(A->_A@A2ANY=>DecisionMaker, _A->A@ANY2A) will be defined at once
builder . localTransitions (). between ( State . A ). and ( State . _A ).
onMutual ( Event . A2ANY , Event . ANY2A ).
perform ( Lists . newArrayList ( new DecisionMaker ( "SomeLocalState" ), null ) );
Informasi lebih lanjut dapat ditemukan di org.squirrelframework.foundation.fsm.samples.DecisionStateSampleTest ;
Anotasi Deklaratif
Cara deklaratif juga disediakan untuk mendefinisikan dan juga memperluas mesin negara. Ini sebuah contoh.
@ States ({
@ State ( name = "A" , entryCallMethod = "entryStateA" , exitCallMethod = "exitStateA" ),
@ State ( name = "B" , entryCallMethod = "entryStateB" , exitCallMethod = "exitStateB" )
})
@ Transitions ({
@ Transit ( from = "A" , to = "B" , on = "GoToB" , callMethod = "stateAToStateBOnGotoB" ),
@ Transit ( from = "A" , to = "A" , on = "WithinA" , callMethod = "stateAToStateAOnWithinA" , type = TransitionType . INTERNAL )
})
interface MyStateMachine extends StateMachine < MyStateMachine , MyState , MyEvent , MyContext > {
void entryStateA ( MyState from , MyState to , MyEvent event , MyContext context );
void stateAToStateBOnGotoB ( MyState from , MyState to , MyEvent event , MyContext context )
void stateAToStateAOnWithinA ( MyState from , MyState to , MyEvent event , MyContext context )
void exitStateA ( MyState from , MyState to , MyEvent event , MyContext context );
...
}
Anotasi dapat didefinisikan di kelas implementasi mesin negara atau antarmuka apa pun yang akan diimplementasikan oleh mesin negara. Ini juga dapat digunakan dicampur dengan API yang lancar, yang berarti mesin status yang ditentukan dalam API yang lancar juga dapat diperluas dengan anotasi ini. (Satu hal yang mungkin perlu Anda perhatikan, metode yang ditentukan dalam antarmuka harus bersifat publik, yang berarti implementasi tindakan pemanggilan metode juga akan bersifat publik bagi pemanggil.)
Pengonversi
Untuk mendeklarasikan status dan peristiwa dalam @State dan @Transit , pengguna perlu mengimplementasikan konverter yang sesuai untuk tipe status(S) dan peristiwa(E). Konversi harus mengimplementasikan antarmuka Converter<T>, yang mengubah status/peristiwa ke/dari String.
public interface Converter < T > extends SquirrelComponent {
/**
* Convert object to string.
* @param obj converted object
* @return string description of object
*/
String convertToString ( T obj );
/**
* Convert string to object.
* @param name name of the object
* @return converted object
*/
T convertFromString ( String name );
}
Kemudian daftarkan konverter ini ke ConverterProvider . misalnya
ConverterProvider . INSTANCE . register ( MyEvent . class , new MyEventConverter ());
ConverterProvider . INSTANCE . register ( MyState . class , new MyStateConverter ());
Catatan: Jika Anda hanya menggunakan Fluent API untuk mendefinisikan mesin status, Anda tidak perlu mengimplementasikan konverter yang sesuai. Dan juga jika kelas Event atau State adalah tipe String atau Enumeration, Anda tidak perlu mengimplementasikan atau mendaftarkan konverter secara eksplisit di sebagian besar kasus.
Contoh Mesin Status Baru
Setelah perilaku mesin status ditentukan oleh pengguna, pengguna dapat membuat instance mesin status baru melalui pembuat. Catatan, setelah instance mesin status dibuat dari pembuat, pembuat tidak dapat lagi digunakan untuk mendefinisikan elemen baru mesin status.
T newStateMachine ( S initialStateId , Object ... extraParams );
Untuk membuat instance mesin status baru dari pembuat mesin status, Anda harus meneruskan parameter berikut.
initialStateId
: Saat dimulai, keadaan awal mesin negara.
extraParams
: Parameter tambahan yang diperlukan untuk membuat instance mesin status baru. Setel ke "Objek baru[0]" tanpa memerlukan parameter tambahan.
A. Jika pengguna meneruskan parameter tambahan saat membuat instance mesin status baru, pastikan bahwa StateMachineBuilderFactory juga telah menentukan jenis parameter tambahan saat membuat pembuat mesin status. Jika tidak, parameter tambahan akan diabaikan. B. Parameter tambahan dapat diteruskan ke instance mesin status dengan dua cara. Salah satunya adalah melalui konstruktor mesin negara yang berarti pengguna perlu mendefinisikan konstruktor dengan tipe dan urutan parameter yang sama untuk instance mesin negara. Cara lain adalah dengan mendefinisikan metode bernama postConstruct
dan juga dengan tipe dan urutan parameter yang sama.
Jika tidak ada parameter tambahan yang perlu diteruskan ke mesin status, pengguna cukup memanggil T newStateMachine(S initialStateId)
untuk membuat instance mesin status baru.
Mesin negara baru dari pembuat mesin negara. (Dalam hal ini, tidak ada parameter tambahan yang perlu diteruskan.)
MyStateMachine stateMachine = builder . newStateMachine ( MyState . Initial );
Memicu Transisi
Setelah mesin negara dibuat, pengguna dapat mengaktifkan peristiwa bersama dengan konteks untuk memicu transisi di dalam mesin negara. misalnya
stateMachine . fire ( MyEvent . Prepare , new MyContext ( "Testing" ));
Mesin Negara yang Belum Diketik
Untuk menyederhanakan penggunaan mesin negara, dan menghindari terlalu banyak tipe generik (misalnya StateMachine<T, S, E, C>) yang mungkin membuat kode sulit dibaca dalam beberapa kasus, namun tetap menjaga bagian penting dari fitur keamanan tipe pada tindakan transisi eksekusi, UntypedStateMachine diimplementasikan untuk tujuan ini.
enum TestEvent {
toA , toB , toC , toD
}
@ Transitions ({
@ Transit ( from = "A" , to = "B" , on = "toB" , callMethod = "fromAToB" ),
@ Transit ( from = "B" , to = "C" , on = "toC" ),
@ Transit ( from = "C" , to = "D" , on = "toD" )
})
@ StateMachineParameters ( stateType = String . class , eventType = TestEvent . class , contextType = Integer . class )
class UntypedStateMachineSample extends AbstractUntypedStateMachine {
// No need to specify constructor anymore since 0.2.9
// protected UntypedStateMachineSample(ImmutableUntypedState initialState,
// Map<Object, ImmutableUntypedState> states) {
// super(initialState, states);
// }
protected void fromAToB ( String from , String to , TestEvent event , Integer context ) {
// transition action still type safe ...
}
protected void transitFromDToAOntoA ( String from , String to , TestEvent event , Integer context ) {
// transition action still type safe ...
}
}
UntypedStateMachineBuilder builder = StateMachineBuilderFactory . create (
UntypedStateMachineSample . class );
// state machine builder not type safe anymore
builder . externalTransition (). from ( "D" ). to ( "A" ). on ( TestEvent . toA );
UntypedStateMachine fsm = builder . newStateMachine ( "A" );
Untuk membangun UntypedStateMachine, pengguna perlu membuat UntypedStateMachineBuilder melalui StateMachineBuilderFactory terlebih dahulu. StateMachineBuilderFactory hanya membutuhkan satu parameter yaitu tipe kelas mesin status untuk membuat UntypedStateMachineBuilder. @StateMachineParameters digunakan untuk mendeklarasikan tipe parameter generik mesin negara. AbstrakUntypedStateMachine adalah kelas dasar dari setiap mesin negara yang belum diketik.
Mesin Status Tidak Peka Konteks
Terkadang transisi keadaan tidak mempedulikan konteks, artinya transisi sebagian besar hanya ditentukan oleh peristiwa. Untuk kasus ini pengguna dapat menggunakan mesin status yang tidak peka konteks untuk menyederhanakan parameter pemanggilan metode. Untuk mendeklarasikan mesin negara yang tidak peka konteks cukup sederhana. Pengguna hanya perlu menambahkan anotasi @ContextInsensitive pada kelas implementasi mesin status. Setelah itu, parameter konteks dapat diabaikan pada daftar parameter metode transisi. misalnya
@ ContextInsensitive
public class ATMStateMachine extends AbstractStateMachine < ATMStateMachine , ATMState , String , Void > {
// no need to add context parameter here anymore
public void transitFromIdleToLoadingOnConnected ( ATMState from , ATMState to , String event ) {
...
}
public void entryLoading ( ATMState from , ATMState to , String event ) {
...
}
}
Penanganan Pengecualian Transisi
Ketika pengecualian terjadi selama transisi status, daftar tindakan yang dijalankan akan dibatalkan dan mesin status akan memasuki status kesalahan, yang berarti instance mesin status tidak dapat lagi memproses peristiwa. Jika pengguna terus mengaktifkan event ke instance mesin status, IllegalStateException akan dibuang. Semua pengecualian yang terjadi selama fase transisi termasuk eksekusi tindakan dan pemanggilan pendengar eksternal akan dimasukkan ke dalam TransitionException (pengecualian tidak dicentang). Saat ini, strategi penanganan pengecualian default sederhana dan kasar dengan hanya terus membuang pengecualian, lihat metode AbstrakStateMachine.afterTransitionCausedException.
protected void afterTransitionCausedException (...) { throw e ; }
Jika mesin negara dapat dipulihkan dari pengecualian ini, pengguna dapat memperluas metode afterTransitionCausedException, dan menambahkan logika pemulihan yang sesuai dalam metode ini. JANGAN lupa untuk mengatur status mesin status kembali normal di akhir. misalnya
@ Override
protected void afterTransitionCausedException ( Object fromState , Object toState , Object event , Object context ) {
Throwable targeException = getLastException (). getTargetException ();
// recover from IllegalArgumentException thrown out from state 'A' to 'B' caused by event 'ToB'
if ( targeException instanceof IllegalArgumentException &&
fromState . equals ( "A" ) && toState . equals ( "B" ) && event . equals ( "ToB" )) {
// do some error clean up job here
// ...
// after recovered from this exception, reset the state machine status back to normal
setStatus ( StateMachineStatus . IDLE );
} else if (...) {
// recover from other exception ...
} else {
super . afterTransitionCausedException ( fromState , toState , event , context );
}
}
Definisikan Status Hierarki
Status hierarki mungkin berisi status bersarang. Status anak mungkin memiliki anak-anak yang bersarang dan sarangnya dapat berlanjut hingga kedalaman apa pun. Ketika status hierarki aktif, hanya satu dan hanya satu status turunannya yang aktif. Status hierarki dapat ditentukan melalui API atau anotasi.
void defineSequentialStatesOn ( S parentStateId , S ... childStateIds );
builder.defineSequentialStatesOn(State.A, State.BinA, StateCinA) mendefinisikan dua status anak "BinA" dan "CinA" di bawah status induk "A", status anak pertama yang ditentukan juga akan menjadi status awal dari status hierarki "A" . Keadaan hierarki yang sama juga dapat ditentukan melalui anotasi, misalnya
@ States ({
@ State ( name = "A" , entryMethodCall = "entryA" , exitMethodCall = "exitA" ),
@ State ( parent = "A" , name = "BinA" , entryMethodCall = "entryBinA" , exitMethodCall = "exitBinA" , initialState = true ),
@ State ( parent = "A" , name = "CinA" , entryMethodCall = "entryCinA" , exitMethodCall = "exitCinA" )
})
Definisikan Keadaan Paralel
Keadaan paralel merangkum sekumpulan keadaan anak yang aktif secara bersamaan ketika elemen induknya aktif. Keadaan paralel dapat ditentukan melalui API atau anotasi keduanya. misalnya
// defines two region states "RegionState1" and "RegionState2" under parent parallel state "Root"
builder . defineParallelStatesOn ( MyState . Root , MyState . RegionState1 , MyState . RegionState2 );
builder . defineSequentialStatesOn ( MyState . RegionState1 , MyState . State11 , MyState . State12 );
builder . externalTransition (). from ( MyState . State11 ). to ( MyState . State12 ). on ( MyEvent . Event1 );
builder . defineSequentialStatesOn ( MyState . RegionState2 , MyState . State21 , MyState . State22 );
builder . externalTransition (). from ( MyState . State21 ). to ( MyState . State22 ). on ( MyEvent . Event2 );
atau
@ States ({
@ State ( name = "Root" , entryCallMethod = "enterRoot" , exitCallMethod = "exitRoot" , compositeType = StateCompositeType . PARALLEL ),
@ State ( parent = "Root" , name = "RegionState1" , entryCallMethod = "enterRegionState1" , exitCallMethod = "exitRegionState1" ),
@ State ( parent = "Root" , name = "RegionState2" , entryCallMethod = "enterRegionState2" , exitCallMethod = "exitRegionState2" )
})
Untuk mendapatkan sub negara bagian saat ini dari keadaan paralel
stateMachine . getSubStatesOn ( MyState . Root ); // return list of current sub states of parallel state
Ketika semua keadaan paralel mencapai keadaan akhir, peristiwa konteks Selesai akan diaktifkan.
Tentukan Peristiwa Konteks
Peristiwa konteks berarti peristiwa yang ditentukan pengguna memiliki konteks yang telah ditentukan sebelumnya di mesin negara. tupai-yayasan mendefinisikan tiga jenis peristiwa konteks untuk kasus penggunaan yang berbeda. Acara Mulai/Terminasi : Acara yang dinyatakan sebagai acara mulai/berakhir akan digunakan ketika mesin status dimulai/dihentikan. Jadi pengguna dapat membedakan pemicu tindakan yang dipanggil, misalnya ketika mesin negara mulai dan memasuki keadaan awalnya, pengguna dapat membedakan tindakan masuk negara ini dipanggil oleh acara awal. Acara Selesai : Ketika semua keadaan paralel mencapai keadaan akhir, acara selesai akan otomatis diaktifkan. Pengguna dapat menentukan transisi berikut berdasarkan acara selesai. Untuk menentukan peristiwa konteks, pengguna memiliki dua cara, anotasi atau API pembuat.
@ ContextEvent ( finishEvent = "Finish" )
static class ParallelStateMachine extends AbstractStateMachine <...> {
}
atau
StateMachineBuilder <...> builder = StateMachineBuilderFactory . create (...);
...
builder . defineFinishEvent ( HEvent . Start );
builder . defineTerminateEvent ( HEvent . Terminate );
builder . defineStartEvent ( HEvent . Finish );
Menggunakan Status Riwayat untuk Menyimpan dan Memulihkan Status Saat Ini
Status semu riwayat memungkinkan mesin negara mengingat konfigurasi statusnya. Transisi yang mengambil status histori sebagai targetnya akan mengembalikan mesin status ke konfigurasi terekam ini. Jika 'tipe' riwayat adalah "dangkal", pemroses mesin negara harus mencatat turunan aktif langsung dari induknya sebelum melakukan transisi apa pun yang keluar dari induknya. Jika 'tipe' riwayat adalah "dalam", pemroses mesin negara harus mencatat semua keturunan aktif dari induknya sebelum melakukan transisi apa pun yang keluar dari induknya. API dan anotasi keduanya didukung untuk menentukan jenis status riwayat. misalnya
// defined history type of state "A" as "deep"
builder . defineSequentialStatesOn ( MyState . A , HistoryType . DEEP , MyState . A1 , MyState . A2 )
atau
@ State ( parent = "A" , name = "A1" , entryCallMethod = "enterA1" , exitCallMethod = "exitA1" , historyType = HistoryType . DEEP )
Catatan: Sebelum 0.3.7, pengguna perlu mendefinisikan "HistoryType.DEEP" untuk setiap tingkat status historis, yang mana hal ini kurang nyaman. (Terima kasih kepada Voskuijlen yang memberikan solusi Issue33). Sekarang pengguna hanya mendefinisikan "HistoryType.DEEP" di tingkat atas status historis, dan semua informasi historis status turunannya akan diingat.
Jenis Transisi
Menurut spesifikasi UML, transisi dapat berupa salah satu dari tiga jenis berikut:
- Transisi Internal Menyiratkan bahwa Transisi, jika dipicu, terjadi tanpa keluar atau memasuki Status sumber (yaitu, tidak menyebabkan perubahan status). Artinya, kondisi masuk atau keluar dari Negara sumber tidak akan diminta. Transisi internal dapat dilakukan bahkan jika StateMachine berada di satu atau lebih Wilayah yang berada di dalam Negara terkait.
- Transisi Lokal Menyiratkan bahwa Transisi, jika dipicu, tidak akan keluar dari Negara gabungan (sumber), namun akan keluar dan masuk kembali ke negara mana pun dalam Negara gabungan yang ada dalam konfigurasi keadaan saat ini.
- Transisi Eksternal Menyiratkan bahwa Transisi, jika dipicu, akan keluar dari Keadaan gabungan (sumber).
tupai-yayasan mendukung API dan anotasi untuk mendeklarasikan semua jenis transisi, misalnya
builder . externalTransition (). from ( MyState . A ). to ( MyState . B ). on ( MyEvent . A2B );
builder . internalTransition (). within ( MyState . A ). on ( MyEvent . innerA );
builder . localTransition (). from ( MyState . A ). to ( MyState . CinA ). on ( MyEvent . intoC )
atau
@ Transitions ({
@ Transition ( from = "A" , to = "B" , on = "A2B" ), //default value of transition type is EXTERNAL
@ Transition ( from = "A" , on = "innerA" , type = TransitionType . INTERNAL ),
@ Transition ( from = "A" , to = "CinA" , on = "intoC" , type = TransitionType . LOCAL ),
})
Pengiriman Peristiwa Polimorfisme
Selama siklus hidup mesin negara, berbagai peristiwa akan dijalankan, misalnya
State Machine Lifecycle Events
|--StateMachineEvent /* Base event of all state machine event */
|--StartEvent /* Fired when state machine started */
|--TerminateEvent /* Fired when state machine terminated */
|--TransitionEvent /* Base event of all transition event */
|--TransitionBeginEvent /* Fired when transition began */
|--TransitionCompleteEvent /* Fired when transition completed */
|--TransitionExceptionEvent /* Fired when transition threw exception */
|--TransitionDeclinedEvent /* Fired when transition declined */
|--TransitionEndEvent /* Fired when transition end no matter declined or complete */
Pengguna dapat menambahkan pendengar untuk mendengarkan StateMachineEvent, yang berarti semua peristiwa yang dipicu selama siklus hidup mesin negara akan ditangkap oleh pendengar ini, misalnya,
stateMachine . addStateMachineListener ( new StateMachineListener <...>() {
@ Override
public void stateMachineEvent ( StateMachineEvent <...> event ) {
// ...
}
});
Dan Pengguna juga dapat menambahkan pendengar untuk mendengarkan TransitionEvent melalui StateMachine.addTransitionListener, yang berarti semua peristiwa yang diaktifkan selama setiap transisi keadaan termasuk TransitionBeginEvent, TransitionCompleteEvent, dan TransitionEndEvent akan ditangkap oleh pendengar ini. Atau pengguna dapat menambahkan pendengar tertentu misalnya TransitionDeclinedListener untuk mendengarkan TransitionDeclinedEvent ketika permintaan transisi ditolak.
Pendengar Peristiwa Deklaratif
Menambahkan pendengar acara di atas ke mesin negara terkadang mengganggu pengguna, dan terlalu banyak tipe generik juga membuat kode jelek untuk dibaca. Untuk menyederhanakan penggunaan mesin negara, yang lebih penting adalah menyediakan integrasi non-invasif, tupai-yayasan menyediakan cara deklaratif untuk menambahkan pendengar acara melalui anotasi berikut, misalnya
static class ExternalModule {
@ OnTransitionEnd
@ ListenerOrder ( 10 ) // Since 0.3.1 ListenerOrder can be used to insure listener invoked orderly
public void transitionEnd () {
// method annotated with TransitionEnd will be invoked when transition end...
// the method must be public and return nothing
}
@ OnTransitionBegin
public void transitionBegin ( TestEvent event ) {
// method annotated with TransitionBegin will be invoked when transition begin...
}
// 'event'(E), 'from'(S), 'to'(S), 'context'(C) and 'stateMachine'(T) can be used in MVEL scripts
@ OnTransitionBegin ( when = "event.name().equals( " toB " )" )
public void transitionBeginConditional () {
// method will be invoked when transition begin while transition caused by event "toB"
}
@ OnTransitionComplete
public void transitionComplete ( String from , String to , TestEvent event , Integer context ) {
// method annotated with TransitionComplete will be invoked when transition complete...
}
@ OnTransitionDecline
public void transitionDeclined ( String from , TestEvent event , Integer context ) {
// method annotated with TransitionDecline will be invoked when transition declined...
}
@ OnBeforeActionExecuted
public void onBeforeActionExecuted ( Object sourceState , Object targetState ,
Object event , Object context , int [] mOfN , Action <?, ?, ?,?> action ) {
// method annotated with OnAfterActionExecuted will be invoked before action invoked
}
@ OnAfterActionExecuted
public void onAfterActionExecuted ( Object sourceState , Object targetState ,
Object event , Object context , int [] mOfN , Action <?, ?, ?,?> action ) {
// method annotated with OnAfterActionExecuted will be invoked after action invoked
}
@ OnActionExecException
public void onActionExecException ( Action <?, ?, ?,?> action , TransitionException e ) {
// method annotated with OnActionExecException will be invoked when action thrown exception
}
}
ExternalModule externalModule = new ExternalModule ();
fsm . addDeclarativeListener ( externalModule );
...
fsm . removeDeclarativeListener ( externalModule );
Dengan melakukan ini, kode modul eksternal tidak perlu mengimplementasikan antarmuka pendengar mesin negara apa pun. Hanya tambahkan sedikit anotasi pada metode yang akan digunakan selama fase transisi. Parameter metode juga bertipe aman, dan secara otomatis akan disimpulkan cocok dengan peristiwa terkait. Ini adalah pendekatan yang baik untuk Pemisahan Kekhawatiran . Pengguna dapat menemukan contoh penggunaan di org.squirrelframework.foundation.fsm.StateMachineLogger .
Metode Perpanjangan Transisi
Setiap peristiwa transisi juga memiliki metode ekstensi yang sesuai pada kelas AbstrakStateMachine yang diperbolehkan untuk diperluas di kelas implementasi mesin status pelanggan.
protected void afterTransitionCausedException ( Exception e , S fromState , S toState , E event , C context ) {
}
protected void beforeTransitionBegin ( S fromState , E event , C context ) {
}
protected void afterTransitionCompleted ( S fromState , S toState , E event , C context ) {
}
protected void afterTransitionEnd ( S fromState , S toState , E event , C context ) {
}
protected void afterTransitionDeclined ( S fromState , E event , C context ) {
}
protected void beforeActionInvoked ( S fromState , S toState , E event , C context ) {
}
Biasanya, pengguna dapat menghubungkan logika pemrosesan bisnis Anda dalam metode ekstensi ini selama setiap transisi keadaan, sementara berbagai pendengar peristiwa berfungsi sebagai batas sistem kontrol berbasis mesin negara, yang dapat berinteraksi dengan modul eksternal (misalnya UI, Auditing, ESB, dan sebagainya. ). Misalnya, pengguna dapat memperluas metode afterTransitionCausedException untuk pembersihan lingkungan ketika pengecualian terjadi selama transisi, dan juga memberi tahu modul antarmuka pengguna untuk menampilkan pesan kesalahan melalui TransitionExceptionEvent.
Tindakan Tertimbang
Pengguna dapat menentukan bobot tindakan untuk menyesuaikan urutan eksekusi tindakan. Tindakan selama masuk/keluar negara dan transisi negara diurutkan dalam urutan menaik sesuai dengan nilai bobotnya. Bobot tindakan adalah 0 secara default. Pengguna memiliki dua cara untuk mengatur bobot tindakan.
Salah satunya adalah menambahkan nomor bobot ke nama metode dan dipisahkan dengan ':'.
// define state entry action 'goEntryD' weight -150
@ State ( name = "D" , entryCallMethod = "goEntryD:-150" )
// define transition action 'goAToC1' weight +150
@ Transit ( from = "A" , to = "C" , on = "ToC" , callMethod = "goAToC1:+150" )
Cara lain adalah mengganti metode bobot kelas Action, misalnya
Action <...> newAction = new Action <...>() {
...
@ Override
public int weight () {
return 100 ;
}
}
tupai-yayasan juga mendukung cara konvensional untuk menyatakan bobot tindakan. Bobot tindakan pemanggilan metode yang namanya dimulai dengan ' sebelum ' akan ditetapkan ke 100, sehingga nama yang dimulai dengan ' setelah ' akan ditetapkan ke -100. Umumnya ini berarti bahwa nama metode tindakan yang dimulai dengan 'sebelum' akan dipanggil terlebih dahulu, sedangkan nama metode tindakan yang dimulai dengan 'sesudah' akan dipanggil terakhir. "method1:ignore" berarti metode1 tidak akan dipanggil.
Untuk informasi lebih lanjut, silakan lihat kasus uji ' org.squirrelframework.foundation.fsm.WeightedActionTest ';
Eksekusi Asinkron
Anotasi @AsyncExecute dapat digunakan pada tindakan pemanggilan metode dan pendengar peristiwa deklaratif untuk menunjukkan bahwa tindakan atau pendengar peristiwa ini akan dieksekusi secara asinkron, misalnya Tentukan metode tindakan yang dipanggil secara asinkron:
@ ContextInsensitive
@ StateMachineParameters ( stateType = String . class , eventType = String . class , contextType = Void . class )
public class ConcurrentSimpleStateMachine extends AbstractUntypedStateMachine {
// No need to specify constructor anymore since 0.2.9
// protected ConcurrentSimpleStateMachine(ImmutableUntypedState initialState,
// Map<Object, ImmutableUntypedState> states) {
// super(initialState, states);
// }
@ AsyncExecute
protected void fromAToB ( String from , String to , String event ) {
// this action method will be invoked asynchronously
}
}
Tentukan acara yang dikirim secara asinkron:
public class DeclarativeListener {
@ OnTransitionBegin
@ AsyncExecute
public void onTransitionBegin (...) {
// transition begin event will be dispatched asynchronously to this listener method
}
}
Tugas eksekusi asinkron akan dikirimkan ke ExecutorService . Pengguna dapat mendaftarkan instance implementasi ExecutorService Anda melalui SquirrelSingletonProvider , misalnya
ExecutorService executorService = Executors . newFixedThreadPool ( 1 );
SquirrelSingletonProvider . getInstance (). register ( ExecutorService . class , executorService );
Jika tidak ada instance ExecutorService yang didaftarkan, SquirrelConfiguration akan menyediakan instance default.
PostProcessor Mesin Status
Pengguna dapat mendaftarkan pemroses pos untuk jenis mesin negara tertentu untuk menambahkan logika proses pasca setelah mesin negara dipakai, misalnya
// 1 User defined a state machine interface
interface MyStateMachine extends StateMachine < MyStateMachine , MyState , MyEvent , MyContext > {
. . .
}
// 2 Both MyStateMachineImpl and MyStateMachineImplEx are implemented MyStateMachine
class MyStateMachineImpl implements MyStateMachine {
. . .
}
class MyStateMachineImplEx implements MyStateMachine {
. . .
}
// 3 User define a state machine post processor
MyStateMachinePostProcessor implements SquirrelPostProcessor < MyStateMachine > {
void postProcess ( MyStateMachine component ) {
. . .
}
}
// 4 User register state machine post process
SquirrelPostProcessorProvider . getInstance (). register ( MyStateMachine . class , MyStateMachinePostProcessor . class );
Untuk kasus ini, ketika pengguna membuat instance MyStateMachineImpl dan MyStateMachineImplEx, pemroses pos terdaftar MyStateMachinePostProcessor akan dipanggil untuk melakukan beberapa pekerjaan.
Ekspor Mesin Negara
SCXMLVisitor dapat digunakan untuk mengekspor definisi mesin status dalam dokumen [SCXML] 2.
SCXMLVisitor visitor = SquirrelProvider . getInstance (). newInstance ( SCXMLVisitor . class );
stateMachine . accept ( visitor );
visitor . convertSCXMLFile ( "MyStateMachine" , true );
BTW, pengguna juga dapat memanggil StateMachine.exportXMLDefinition(true) untuk mengekspor definisi XML yang dipercantik. DotVisitor dapat digunakan untuk menghasilkan diagram keadaan yang dapat dilihat dengan [GraphViz] 3.
DotVisitor visitor = SquirrelProvider . getInstance (). newInstance ( DotVisitor . class );
stateMachine . accept ( visitor );
visitor . convertDotFile ( "SnakeStateMachine" );
Impor Mesin Negara
UntypedStateMachineImporter dapat digunakan untuk mengimpor definisi serupa SCXML mesin negara yang diekspor oleh SCXMLVisitor atau definisi tulisan tangan. UntypedStateMachineImporter akan membangun UntypedStateMachineBuilder sesuai dengan definisi yang nantinya dapat digunakan untuk membuat instance mesin negara.
UntypedStateMachineBuilder builder = new UntypedStateMachineImporter (). importDefinition ( scxmlDef );
ATMStateMachine stateMachine = builder . newAnyStateMachine ( ATMState . Idle );
Catatan: UntypedStateMachineImporter menyediakan gaya XML untuk mendefinisikan mesin status seperti API pembuat mesin status atau anotasi deklaratif. Definisi serupa SCXML tidak sama dengan SCXML standar.
Simpan/Muat Data Mesin Status
Pengguna dapat menyimpan data mesin negara ketika mesin negara dalam status menganggur.
StateMachineData . Reader < MyStateMachine , MyState , MyEvent , MyContext >
savedData = stateMachine . dumpSavedData ();
Dan juga pengguna dapat memuat data tersimpan di atas ke mesin status lain yang statusnya dihentikan atau baru saja diinisialisasi.
newStateMachineInstance . loadSavedData ( savedData );
CATATAN : Data mesin status dapat diserialkan ke/dideserialisasi dari string yang dikodekan Base64 dengan bantuan kelas ObjectSerializableSupport .
Konfigurasi Mesin Negara
Saat membuat instance mesin status baru, pengguna dapat mengonfigurasi perilakunya melalui StateMachineConfiguration , misalnya
UntypedStateMachine fsm = builder . newUntypedStateMachine ( "a" ,
StateMachineConfiguration . create (). enableAutoStart ( false )
. setIdProvider ( IdProvider . UUIDProvider . getInstance ()),
new Object [ 0 ]); // since 0.3.0
fsm . fire ( TestEvent . toA );
Contoh kode di atas digunakan untuk membuat instance mesin status dengan UUID sebagai pengidentifikasinya dan menonaktifkan fungsi mulai otomatis. StateMachineConfigure juga dapat disetel pada pembuat mesin status yang berarti semua instance mesin status yang dibuat oleh builder.newStateMachine(S initialStateId)
atau builder.newStateMachine(S initialStateId, Object... extraParams)
akan menggunakan konfigurasi ini.
Diagnosis Mesin Negara
StateMachineLogger digunakan untuk mengamati status internal mesin negara, seperti kinerja eksekusi, urutan pemanggilan tindakan, kemajuan transisi dan sebagainya, misalnya
StateMachine <?,?,?,?> stateMachine = builder . newStateMachine ( HState . A );
StateMachineLogger fsmLogger = new StateMachineLogger ( stateMachine );
fsmLogger . startLogging ();
...
stateMachine . fire ( HEvent . B2A , 1 );
...
fsmLogger . terminateLogging ();
-------------------------------------------------------------------------------------------
Console Log :
HierachicalStateMachine : Transition from "B2a" on "B2A" with context "1" begin .
Before execute method call action "leftB2a" ( 1 of 6 ).
Before execute method call action "exitB2" ( 2 of 6 ).
...
Before execute method call action "entryA1" ( 6 of 6 ).
HierachicalStateMachine : Transition from "B2a" to "A1" on "B2A" complete which took 2 ms .
...
Karena logger mesin status v0.3.0 dapat digunakan dengan cara yang lebih mudah hanya dengan menyetel StateMachineConfiguration, aktifkan mode debug ke masa depan, misalnya
StateMachine<?,?,?,?> stateMachine = builder.newStateMachine(HState.A,
StateMachineConfiguration.create().enableDebugMode(true),
new Object[0]);
StateMachinePerformanceMonitor dapat digunakan untuk memantau informasi kinerja eksekusi mesin status, termasuk jumlah waktu transisi total, waktu transisi rata-rata yang dikonsumsi, dan sebagainya, misalnya
final UntypedStateMachine fsm = builder . newStateMachine ( "D" );
final StateMachinePerformanceMonitor performanceMonitor =
new StateMachinePerformanceMonitor ( "Sample State Machine Performance Info" );
fsm . addDeclarativeListener ( performanceMonitor );
for ( int i = 0 ; i < 10000 ; i ++) {
fsm . fire ( FSMEvent . ToA , 10 );
fsm . fire ( FSMEvent . ToB , 10 );
fsm . fire ( FSMEvent . ToC , 10 );
fsm . fire ( FSMEvent . ToD , 10 );
}
fsm . removeDeclarativeListener ( performanceMonitor );
System . out . println ( performanceMonitor . getPerfModel ());
-------------------------------------------------------------------------------------------
Console Log :
========================== Sample State Machine Performance Info ==========================
Total Transition Invoked : 40000
Total Transition Failed : 0
Total Transition Declained : 0
Average Transition Comsumed : 0.0004 ms
Transition Key Invoked Times Average Time Max Time Min Time
C --{ ToD , 10 }-> D 10000 0.0007 ms 5 ms 0 ms
B --{ ToC , 10 }-> C 10000 0.0001 ms 1 ms 0 ms
D --{ ToA , 10 }-> A 10000 0.0009 ms 7 ms 0 ms
A --{ ToB , 10 }-> B 10000 0.0000 ms 1 ms 0 ms
Total Action Invoked : 40000
Total Action Failed : 0
Average Action Execution Comsumed : 0.0000 ms
Action Key Invoked Times Average Time Max Time Min Time
instan ... Test$1 40000 0.0000 ms 1 ms 0 ms
========================== Sample State Machine Performance Info ==========================
Menambahkan @LogExecTime pada metode tindakan akan mengeluarkan waktu eksekusi metode tersebut. Dan juga menambahkan @LogExecTime pada kelas mesin negara akan logout semua waktu eksekusi metode tindakan. Misalnya, waktu eksekusi metode transitFromAToBonGoToB akan dikeluarkan.
@ LogExecTime
protected void transitFromAToBOnGoToB ( MyState from , MyState to , MyEvent event , MyContext context )
Status Jangka Waktu
Keadaan berwaktu adalah keadaan yang dapat menunda atau secara berkala memicu kejadian tertentu setelah keadaan dimasukkan. Tugas berjangka waktu akan dikirimkan ke ScheduledExecutorService . Pengguna dapat mendaftarkan instance implementasi ScheduledExecutorService Anda melalui SquirrelSingletonProvider , misalnya
ScheduledExecutorService scheduler = Executors . newScheduledThreadPool ( 1 );
SquirrelSingletonProvider . getInstance (). register ( ScheduledExecutorService . class , scheduler );
Jika tidak ada instance ScheduledExecutorService yang didaftarkan, SquirrelConfiguration akan menyediakan instance default. Setelah itu, status waktunya dapat ditentukan oleh pembuat mesin status, misalnya
// after 50ms delay fire event "FIRST" every 100ms with null context
builder . defineTimedState ( "A" , 50 , 100 , "FIRST" , null );
builder . internalTransition (). within ( "A" ). on ( "FIRST" );
CATATAN : Pastikan status waktunya harus ditentukan sebelum menjelaskan transisi atau tindakan masuk/keluarnya. timeInterval kurang dari atau sama dengan 0 akan dianggap hanya dieksekusi sekali setelah inisialDelay .
Linked State (disebut Submachine State)
Status tertaut menentukan penyisipan spesifikasi mesin status submesin. Mesin negara yang berisi keadaan tertaut disebut mesin negara yang memuatnya. Mesin negara yang sama dapat menjadi mesin ringan lebih dari satu kali dalam konteks satu mesin negara yang memuatnya.
Keadaan tertaut secara semantik setara dengan keadaan gabungan. Wilayah mesin negara submesin adalah wilayah negara gabungan. Tindakan masuk, keluar, dan perilaku serta transisi internal didefinisikan sebagai bagian dari negara. Status submesin adalah mekanisme dekomposisi yang memungkinkan pemfaktoran perilaku umum dan penggunaannya kembali. Status tertaut dapat ditentukan dengan kode contoh berikut.
builderOfTestStateMachine . definedLinkedState ( LState . A , builderOfLinkedStateMachine , LState . A1 );
Dukungan JMX
Sejak 0.3.3, pengguna dapat memonitor instance mesin status dari jarak jauh (mis. status saat ini, nama) dan mengubah konfigurasi (mis. beralih logging/beralih monitor kinerja/peristiwa kebakaran jarak jauh) saat runtime. Semua informasi instance mesin negara akan berada di domain "org.squirrelframework". Kode contoh berikut menunjukkan cara mengaktifkan dukungan JMX.
UntypedStateMachineBuilder builder = StateMachineBuilderFactory . create (...);
builder . setStateMachineConfiguration ( StateMachineConfiguration . create (). enableRemoteMonitor ( true ));
CATATAN : Dukungan fitur JMX tidak digunakan lagi sejak 0.3.9-SNAPSHOT.
Lihat file CONTOH.
Lihat file CATATAN RELEASE.
Untuk pembaruan terkini ikuti twitter saya @hhe11 atau +HeHenry
Untuk diskusi atau pertanyaan silakan bergabung dengan grup mesin negara tupai
Untuk masalah atau persyaratan apa pun, silakan kirimkan masalah
Jika Anda menggunakan kode Squirrel State Machine dalam aplikasi Anda, saya akan sangat menghargai jika Anda memberi tahu penulis tentang hal itu (email: [email protected]) seperti ini :
Perihal: Teks Pemberitahuan Penggunaan Squirrel State Machine: Saya menggunakan Squirrel State Machine <lib_version> di <project_name> - http://link_to_project. saya [mengizinkan | jangan izinkan] menyebutkan proyek saya di bagian "Siapa yang menggunakan Squirrel State Machine" di GitHub.