書きすぎない方法。 魔法はありません

A. img

最近、Habréに関する最初の記事を公​​開しました。 そして、最初のパンケーキが私の頭に飛び込んできました。 githubで12kのビューと4つ星に加えて...まあ、それは私のせいです。ロシア語の言語と文学のレッスンでナンセンスをする必要はありませんでした。 私が正しく理解していれば、問題は私がすぐにその場所に行ったことでした。 すべてを額に捨てました。 いわば、両親に会えなかった。 そして、それはどんな種類のJetaで、どのように機能しますか、舞台裏で何が起こりますか? 私はどのような魔法なのか ...プロジェクトで魔法を必要とする人はいませんか?







「誰かがあなたのライブラリを必要としているという自信はどこにありますか?」 平均的なhabrochaninが尋ねます。 そこから、毎日、別の注釈を切るか、単にコードを見ているだけで、 「神様、すばらしい!」と思います 。 誰がそのようなことを拒否しますか?







さて、最初に順番にしましょう。







Jetaは、注釈のソースコードを生成するためのフレームワークであり、 javax.annotation.processing



上に構築されています。 注釈処理とは、たとえばhereまたはhereを読むことができます 。 要するに、これはJava 1.5(しかし1.6の方が良い)で利用可能な文書化されていない技術であり、プロジェクトのAST 、独自のプロセッサを通過し、あなたに合った方法で注釈を処理することができます。 そして、コンパイルの直前にすべてを行います。 daggerdagger 2jetaandroid注釈などのモンスターがこれに基づいて構築されています。 私の意見では、 Java Annotation Processingは非常に過小評価されている技術であり、私のような反射恐怖症にとっては、これがプログラム可能な唯一の方法です。 幸いなことに、Androidの登場により、状況は変わり始めました。 それは美しいに参加する時間です!







プロジェクトに取り組んでいる間に私が追求した目標は次のとおりです。









しかし、それだけではありません。「バッテリーが含まれています」! Dependecy InjectionEvent BusValidators など 、快適な作業に必要なものはすべて既に書かれています 上記のすべての原則に従います。 また、 コレクターはそのまま使用できます。 フレームワークがどのように配置されるかを理解するのは彼らの例です。







球状牛



プロジェクトにイベントハンドラーがあるとします。 どんな種類のイベントでも構いません。これらはプッシュメッセージ、ステートマシンの状態、またはユーザーコマンドです。 ああ! ユーザーからのコマンドにしましょう。 さらに、チャットボットを作成するトピックが関連性を持つようになりました。







そのため、ハンドラーが必要です。







 public interface CommandHandler { void handle(); } public class GreetingCommand implements CommandHandler { @Override public void handle() { System.out.print("Hi! How are you?"); } } public class ExitCommand implements CommandHandler { @Override public void handle() { System.out.print("Bye!"); System.exit(0); } }
      
      





CPU







 public class CommandProcessor { private Map<String, CommandHandler> handlers = new HashMap<>(); public void loop() { System.out.println("Input command. Type 'exit' to finish."); Scanner input = new Scanner(System.in); while (true) { String command = input.next(); CommandHandler handler = handlers.get(command); if(handler == null) System.out.println("Unknown command '" + command + "'. Try again"); else handler.handle(); } } public static void main(String[] args) { new CommandProcessor().loop(); } }
      
      





次に、ユーザーコマンドを対応するハンドラーに関連付ける必要があります。 XML modが沈静化したことは知っていますが、それにもかかわらず、ほとんどのプログラマーがXMLの助けを借りてこのような問題を解決しています。 まあ、XMLはとてもXMLです..







 <?xml version="1.0" encoding="utf-8" ?> <handlers> <handler command="greet">org.brooth.jeta.samples.command_handler.commands.GreetingCommand</hanler> <handler command="exit">org.brooth.jeta.samples.command_handler.commands.ExitCommand</hanler> </handlers>
      
      





パーシム!







 public class CommandProcessor { private Map<String, CommandHandler> handlers = new HashMap<>(); public CommandProcessor() { parseHandlers(); } private void parseHandlers() { try { DocumentBuilder documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); Document document = documentBuilder.parse("handlers.xml"); NodeList nodes = document.getDocumentElement().getElementsByTagName("handler"); for (int i = 0; i < nodes.getLength(); i++) { Node node = nodes.item(i); handlers.put(node.getAttributes().getNamedItem("command").getTextContent(), (CommandHandler) Class.forName(node.getTextContent()).newInstance()); } } catch (Exception e) { throw new RuntimeException("Failed to parse handlers.xml", e); } } public void loop() {...} public static void main(String[] args) {...} }
      
      





実行、確認してください!







 Input command. Type 'exit' to finish. greet Hi! How are you? fine! Unknown command 'fine!'. Try again exit Bye!
      
      





うまくいきます! 他に何かを書きましょう! time



コマンドを使用して現在の時刻を表示します!







 public class TimeCommand implements CommandHandler { @Override public void handle() { System.out.println("It's " + new SimpleDateFormat("HH:mm").format(new Date())); } }
      
      





私たちは始めます..







 Input command. Type 'exit' to finish. time Unknown command 'time'. Try again
      
      





地獄 さて、緊張する必要はありません。 次に、 handers.xml



新しいハンドラーをすばやく追加して再起動します。 それではビジネス! これは実際のエンタープライズプロジェクトではありません。5分かかり、さらに多くのプロジェクトが開始されます。 さて、あなたは理解しています...







動作中のジェット



そして、Jetaは私たちに何を提供しますか? Jetaはコレクターを提供します! ハンドラーはコンパイル時に自動的になります、私はそれを保証します!







ライブラリを接続します( build.gradle



):







 buildscript { repositories { maven { url 'https://plugins.gradle.org/m2/' } } dependencies { classpath 'net.ltgt.gradle:gradle-apt-plugin:0.9' } } apply plugin: 'java' apply plugin: 'net.ltgt.apt' repositories { jcenter() } dependencies { apt 'org.brooth.jeta:jeta-apt:+' compile 'org.brooth.jeta:jeta:+' }
      
      





Command



アノテーションを作成し、ハンドラーでハングさせます。







 @Target(TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Command { String value(); } @Command("exit") public class ExitCommand implements CommandHandler {...} @Command("greet") public class GreetingCommand implements CommandHandler {...} @Command("time") public class TimeCommand implements CommandHandler {...}
      
      





CommandProcessor



を完成させます:







 @TypeCollector(Command.class) public class CommandProcessor { private Map<String, CommandHandler> handlers = new HashMap<>(); public CommandProcessor() { //parseHandlers(); collectHandlers(); } private void collectHandlers() { Metasitory metasitory = new MapMetasitory(""); List<Class<?>> types = new TypeCollectorController(metasitory, getClass()).getTypes(Command.class); for (Class handlerClass : types) { try { Command command = (Command) handlerClass.getAnnotation(Command.class); handlers.put(command.value(), (CommandHandler) handlerClass.newInstance()); } catch (Exception e) { throw new RuntimeException("Failed to collect handlers", e); } } } private void parseHandlers() {...} public void loop() {...} public static void main(String[] args) {...} }
      
      





そして...







 Input command. Type 'exit' to finish. greet Hi! How are you? fine Unknown command 'fine'. Try again time It's 16:28 exit Bye!
      
      







舞台裏



ランタイム

魔法なしで約束した? だから、順番に:







マスター



複雑なことはありません。フレームワークのコンテキストでは、マスターはメトコードが生成されるクラスです。 私たちのCommandProcessor



、これはCommandProcessor



です。 @TypeCollector



アノテーションを使用します。







メタコード



メタコード-生成された(マスター用)クラス。 マスターと同じパッケージにあり(物理的ではなく、心の平穏)、 <Master name> + "_Metacode"



という複合名を持っています。 この例では、これはCommandProcessor_Metacode



です。







 public class CommandProcessor_Metacode implements Metacode<CommandProcessor>, TypeCollectorMetacode { @Override public Class<CommandProcessor> getMasterClass() { return CommandProcessor.class; } @Override public List<Class<?>> getTypeCollection(Class<? extends Annotation> annotation) { if(annotation == org.brooth.jeta.samples.command_handler.Command.class) { List<Class<?>> result = new ArrayList<Class<?>>(3); result.add(org.brooth.jeta.samples.command_handler.commands.ExitCommand.class); result.add(org.brooth.jeta.samples.command_handler.commands.TimeCommand.class); result.add(org.brooth.jeta.samples.command_handler.commands.GreetingCommand.class); return result; } return null; } }
      
      





メタシトリー



奇妙な名前、私は知っています。 しかし、毎回「メタコードリポジトリ」と言う気にはなりません。







Metasitoryは 、ご想像のとおりメタコードへのリンクのリポジトリです。 図ではDB2のように見えますが、恐れる必要はありません。デフォルトではIdentityHashMap



(ただし、冒頭で述べたように、 DB2で実装を記述できます。プルリクエストなしでのみ可能です)。 より具体的には、デフォルトのMetasitory実装はMapMetasitoryです。 これは、 CommandProcessor



ソースコードで気づいたかもしれません。 MapMetasitoryはいわゆるMetasitoryContainersを使用します。これはMetacode同様に 、コンパイル時に自動的に生成されます。 ただし、 IdentityHashMap



はメタコード付きのコンテキストが既に保存されています。







 public class MetasitoryContainer implements MapMetasitoryContainer { @Override public Map<Class<?>, MapMetasitoryContainer.Context> get() { Map<Class<?>, MapMetasitoryContainer.Context> result = new IdentityHashMap<>(); result.put(org.brooth.jeta.samples.command_handler.CommandProcessor.class, new MapMetasitoryContainer.Context( org.brooth.jeta.samples.command_handler.CommandProcessor.class, new org.brooth.jeta.Provider<org.brooth.jeta.samples.command_handler.CommandProcessor_Metacode>() { public org.brooth.jeta.samples.command_handler.CommandProcessor_Metacode get() { return new org.brooth.jeta.samples.command_handler.CommandProcessor_Metacode(); }}, new Class[] { org.brooth.jeta.collector.TypeCollector.class })); return result; } }
      
      





コンテキストは、ウィザードクラス、 メタコードプロバイダー (メタコードインスタンスを作成)、および使用される注釈のリストの3つのフィールドで構成されます。 このセットはCriteriaを検索するのに十分です:







基準



ここではすべてが明確であり、 Criteriaの助けを借りて、 Metasitoryへのリクエストが説明されています。 現在のバージョン(2.3)では、検索は次の基準でサポートされています。









この例では、 masterEq



で十分です-なぜなら CommandProcessor_Metacodeのみに関心があります。







コントローラー



最後の要素(およびオプションで)はコントローラーです。 Criteriaを使用して、目的のコントローラーに戻り、Metasitoryに適切なメタコードを要求し、必要なメソッドを「プル」します。 おそらく彼は他のいくつかの変換またはチェックを行いますが、それはすべて実装に依存します。 この例では、 TypeCollectorController



を使用しTypeCollectorController



CommandProcessorのソースコードにも表示されます)。







 public class TypeCollectorController { private Collection<Metacode<?>> metacodes; public TypeCollectorController(Metasitory metasitory, Class<?> masterClass) { metacodes = metasitory.search(new Criteria.Builder() .masterEq(masterClass) .build()); } public List<Class<?>> getTypes(Class<? extends Annotation> annotation) { assert annotation != null; if (metacodes.isEmpty()) return null; return ((TypeCollectorMetacode) metacodes.iterator().next()).getTypeCollection(annotation); } }
      
      





ナフは言った







PS



今回ライブラリに興味を示した場合、次の記事はJeta Dependency Injectionについてです。 何か言いたいことがあるでしょう。







頑張って書いてください







サンプルソースコード

公式サイト

GitHubのJeta








All Articles