1. แนวคิดพื้นฐานบางอย่าง
ก่อนที่เราจะเริ่มต้นเราต้องประกาศสิ่งสำคัญ: เราไม่ได้พูดถึงคำอธิบายประกอบที่ประมวลผลผ่านกลไกการสะท้อนกลับที่รันไทม์ แต่พูดคุยเกี่ยวกับคำอธิบายประกอบที่ดำเนินการในเวลาคอมไพล์
อะไรคือความแตกต่างระหว่างคำอธิบายประกอบเวลาคอมไพล์และคำอธิบายประกอบเวลาทำงาน? ในความเป็นจริงมันไม่ใหญ่ส่วนใหญ่เป็นเพราะปัญหาด้านประสิทธิภาพ คำอธิบายประกอบรันไทม์ส่วนใหญ่พึ่งพาการสะท้อนทั้งหมดและประสิทธิภาพของการสะท้อนช้ากว่าของพื้นเมืองดังนั้นจะมีความล่าช้าในเครื่องที่มีหน่วยความจำน้อยลงและซีพียูที่ไม่ดี อย่างไรก็ตามคำอธิบายประกอบจะไม่มีปัญหานี้เลยเพราะในกระบวนการรวบรวมของเรา (คลาส Java->) เครื่องหมายคำอธิบายประกอบบางอย่างใช้เพื่อสร้างคลาสหรือไฟล์บางส่วนแบบไดนามิกดังนั้นจึงไม่มีส่วนเกี่ยวข้องกับการดำเนินการ APK ของเราดังนั้นจึงไม่มีปัญหาด้านประสิทธิภาพ ดังนั้นหากโครงการโอเพ่นซอร์สที่มีชื่อเสียงมากขึ้นใช้ฟังก์ชั่นคำอธิบายประกอบมันมักจะใช้คำอธิบายประกอบเวลาคอมไพล์
ตัวประมวลผลคำอธิบายประกอบเป็นเครื่องมือที่มาพร้อมกับ Javac ซึ่งใช้ในการสแกนและประมวลผลข้อมูลคำอธิบายประกอบในช่วงระยะเวลาการรวบรวม คุณสามารถลงทะเบียนโปรเซสเซอร์คำอธิบายประกอบของคุณเองสำหรับคำอธิบายประกอบบางอย่าง ที่นี่ฉันคิดว่าคุณเข้าใจแล้วว่าคำอธิบายประกอบคืออะไรและจะปรับแต่งได้อย่างไร หากคุณยังไม่เข้าใจคำอธิบายประกอบคุณสามารถตรวจสอบเอกสารอย่างเป็นทางการได้ ตัวประมวลผลคำอธิบายประกอบมีอยู่แล้วใน Java 5 แต่ไม่มี API จนกว่าจะมี Java 6 (เปิดตัวในเดือนธันวาคม 2549) ใช้เวลาสักครู่ก่อนที่ผู้ใช้ Java จะตระหนักถึงพลังของโปรเซสเซอร์คำอธิบายประกอบ ดังนั้นมันจึงกลายเป็นที่นิยมในช่วงไม่กี่ปีที่ผ่านมา
โปรเซสเซอร์ที่มีคำอธิบายประกอบที่เฉพาะเจาะจงใช้ซอร์สโค้ด Java (หรือรวบรวม bytecode) เป็นอินพุตจากนั้นสร้างไฟล์บางไฟล์ (โดยปกติแล้วไฟล์. java) เป็นเอาต์พุต นั่นหมายความว่าอย่างไร? คุณสามารถสร้างรหัส Java! รหัส Java เหล่านี้อยู่ในไฟล์. java ที่สร้างขึ้น ดังนั้นคุณไม่สามารถเปลี่ยนคลาส Java ที่มีอยู่เช่นการเพิ่มวิธีการ ไฟล์ Java ที่สร้างขึ้นเหล่านี้จะถูกรวบรวมโดย Javac เช่นซอร์สโค้ด Java ที่เขียนด้วยตนเองอื่น ๆ
การประมวลผลคำอธิบายประกอบจะดำเนินการในขั้นตอนการรวบรวม หลักการของมันคือการอ่านซอร์สโค้ด Java แยกวิเคราะห์คำอธิบายประกอบแล้วสร้างรหัส Java ใหม่ รหัส Java ที่สร้างขึ้นใหม่ได้ถูกรวบรวมลงใน Java bytecode ในที่สุด โปรเซสเซอร์คำอธิบายประกอบไม่สามารถเปลี่ยนคลาส Java Read Java เช่นการเพิ่มหรือลบวิธี Java
2. AbstractProcessor
มาดู API ของโปรเซสเซอร์กันเถอะ โปรเซสเซอร์ทั้งหมดสืบทอดบทคัดย่อ AbstractProcessor ดังที่แสดงด้านล่าง:
แพ็คเกจ com.example; นำเข้า java.util.linkedhashset; นำเข้า java.util.set; นำเข้า Javax.annotation.processing.abstractprocessor; นำเข้า javax.annotation.processing.processingenvironment; javax.annotation.processing.supportedannotationTypes; นำเข้า Javax.annotation.processing.supportedSourceVersion; นำเข้า Javax.lang.model.sourceversion; นำเข้า Javax.lang.model.element.typeelement; ขยายการพิมพ์> ประกาศ, env roundenvironment) {return false; } @Override ชุดสาธารณะ <String> getSupportedAntationTypes () {set <String> annotataions = ใหม่ linkedHashSet <String> (); Annotataions.add ("com.example.myannotation"); กลับ annotataions; } @Override Public SourceVersion GetSupportedSourceVersion () {return sourceVersion.latestsupported (); } @Override เป็นโมฆะที่ซิงโครไนซ์สาธารณะ (ProcessingEnvironment ProcessingEnv) {super.init (processingEnv); -init (processingEnvironment processingenv): คลาสโปรเซสเซอร์คำอธิบายประกอบทั้งหมดจะต้องมีตัวสร้างแบบไม่มีพารามิเตอร์ อย่างไรก็ตามมีวิธีการพิเศษ init () ซึ่งเรียกว่าโดยเครื่องมือการประมวลผลคำอธิบายประกอบโดยใช้การประมวลผลสิ่งแวดล้อมเป็นพารามิเตอร์ ProcessingEnvironment ให้องค์ประกอบประเภทเครื่องมือที่ใช้งานได้จริงประเภทประเภทและตัวกรอง เราจะใช้มันในภายหลัง
process(Set<? extends TypeElement> annoations, RoundEnvironment env) : สิ่งนี้คล้ายกับวิธีการหลัก () ของแต่ละโปรเซสเซอร์ คุณสามารถเข้ารหัสและใช้การสแกนประมวลผลคำอธิบายประกอบและสร้างไฟล์ Java ในวิธีนี้ การใช้พารามิเตอร์ RoundenVironment คุณสามารถสอบถามองค์ประกอบที่มีคำอธิบายประกอบด้วยคำอธิบายประกอบบางอย่าง (ข้อความต้นฉบับ: คุณสามารถสอบถามสำหรับองค์ประกอบที่มีคำอธิบายประกอบด้วยคำอธิบายประกอบที่แน่นอน) เราจะเห็นรายละเอียดในภายหลัง
getSupportedAnnotationTypes(): ในวิธีนี้คุณต้องระบุคำอธิบายประกอบที่ควรลงทะเบียนโดยหน่วยประมวลผลคำอธิบายประกอบ โปรดทราบว่าค่าส่งคืนเป็นคอลเลกชันสตริงที่มีชื่อเต็มของประเภทคำอธิบายประกอบที่โปรเซสเซอร์คำอธิบายประกอบของคุณต้องการประมวลผล กล่าวอีกนัยหนึ่งคุณกำหนดไว้ที่นี่ว่าคำอธิบายประกอบใดที่โปรเซสเซอร์คำอธิบายประกอบของคุณกำลังจะจัดการ
getSupportedSourceVersion() : ใช้เพื่อระบุเวอร์ชัน Java ที่คุณใช้ โดยปกติคุณควรส่งคืน SourceVersion.latestSupported() อย่างไรก็ตามหากคุณมีเหตุผลเพียงพอที่จะยึดติดกับ Java 6 คุณยังสามารถส่งคืน SourceVersion.RELEASE_6 ฉันขอแนะนำให้ใช้ SourceVersion.latestSupported() ใน Java 7 คุณยังสามารถใช้คำอธิบายประกอบเพื่อแทนที่การเขียนใหม่ getSupportedAnnotationTypes() และ getSupportedSourceVersion() ดังที่แสดงด้านล่าง:
@SupportedSourceVersion (value = sourceVersion.RELEASE_7) @SupportedAnnotationTypes ({// ชุดของประเภทคำอธิบายประกอบแบบเต็มรูปแบบ Qulified "com.example.myannotation" TypeElement> ประกาศ, env roundenvironment) {return false; } @Override เป็นโมฆะที่ซิงโครไนซ์สาธารณะ (ProcessingEnvironment ProcessingEnv) {super.init (processingEnv); - เนื่องจากปัญหาความเข้ากันได้โดยเฉพาะอย่างยิ่งสำหรับ Android ฉันขอแนะนำให้เขียนใหม่ getSupportedAnnotationTypes() และ getSupportedSourceVersion() แทนที่จะใช้ @SupportedAnnotationTypes และ @SupportedSourceVersion
สิ่งต่อไปที่คุณต้องรู้คือ: โปรเซสเซอร์คำอธิบายประกอบทำงานใน JVM ของตัวเอง ใช่คุณอ่านถูกต้อง Javac เริ่มเครื่องเสมือน Java ที่สมบูรณ์เพื่อเรียกใช้โปรเซสเซอร์คำอธิบายประกอบ นั่นหมายความว่าอย่างไร? คุณสามารถใช้อะไรก็ได้ที่คุณใช้ในโปรแกรม Java ปกติ ใช้ GUAVA! คุณสามารถใช้เครื่องมือฉีดพึ่งพาได้เช่นกริชหรือห้องสมุดชั้นเรียนอื่น ๆ ที่คุณต้องการใช้ แต่อย่าลืมว่าแม้ว่าจะเป็นเพียงโปรเซสเซอร์เล็ก ๆ คุณควรให้ความสนใจกับการใช้อัลกอริทึมที่มีประสิทธิภาพและรูปแบบการออกแบบเช่นเดียวกับที่คุณทำในการพัฒนาโปรแกรม Java อื่น ๆ
3. ลงทะเบียนโปรเซสเซอร์ของคุณ
คุณอาจถามว่า "จะลงทะเบียนโปรเซสเซอร์คำอธิบายประกอบของฉันไปยัง Javac ได้อย่างไร" คุณต้องระบุไฟล์. jar เช่นเดียวกับไฟล์. jar อื่น ๆ คุณแพ็คเกจโปรเซสเซอร์คำอธิบายประกอบที่รวบรวมไว้แล้วลงในไฟล์นี้ และในไฟล์. jar ของคุณคุณต้องบรรจุไฟล์พิเศษ javax.annotation.processing.processor ลงในไดเรกทอรี meta-inf/บริการ ดังนั้นโครงสร้างไดเรกทอรีไฟล์. jar ของคุณจึงเป็นแบบนี้:
myprocess.jar -com -example -myprocess.class -meta -inf -services -javax.annotation.processing.processor
เนื้อหาของ javax.annotation.processing.processor เป็นรายการและแต่ละบรรทัดเป็นชื่อเต็มของโปรเซสเซอร์คำอธิบายประกอบ ตัวอย่างเช่น:
com.example.myprocess
com.example.anotherprocess
4. ตัวอย่าง: แบบจำลองโรงงาน
ปัญหาที่เราต้องการแก้ไขคือ: เราต้องการใช้ร้านพิซซ่าซึ่งให้บริการลูกค้าด้วยพิซซ่าสองตัว (Margherita และ Calzone) รวมถึงของหวาน Tiramisu (Tiramisu)
มื้ออาหารสาธารณะ {public float getPrice ();} ชั้นเรียนสาธารณะ Margheritapizza ใช้อาหาร {@Override สาธารณะ Float GetPrice () {return 6.0f; }} ชั้นเรียนสาธารณะ CalzonePizza ใช้อาหาร {@Override Public Float GetPrice () {return 8.5f; }} คลาสสาธารณะ Tiramisu ใช้อาหาร {@Override Public Float GetPrice () {return 4.5f; }} ชั้นเรียนสาธารณะ pizzastore {คำสั่งอาหารสาธารณะ (String MealName) {ถ้า (null == mealname) {โยน unlegalargumentException ใหม่ ("ชื่อของมื้ออาหารเป็นโมฆะ!"); } if ("margherita" .equals (mealname)) {ส่งคืน margheritapizza ใหม่ (); } if ("calzone" .equals (mealname)) {ส่งคืน calzonepizza ใหม่ (); } if ("tiramisu" .equals (mealname)) {ส่งคืน tiramisu ใหม่ (); } โยน unlegalargumentException ใหม่ ("ไม่รู้จักมื้ออาหาร" " + mealname +" '"); } readConsole สตริงส่วนตัว () {Scanner Scanner = สแกนเนอร์ใหม่ (System.in); สตริงมื้อ = scanner.nextline (); Scanner.close (); อาหารกลับ; } โมฆะคงที่สาธารณะหลัก (สตริง [] args) {system.out.println ("ยินดีต้อนรับสู่ Pizza Store"); pizzastore pizzastore = new pizzastore (); มื้ออาหาร = pizzastore.order (readconsole ()); System.out.println ("Bill: $" + Meal.getPrice ()); -อย่างที่คุณเห็นในวิธีการสั่งซื้อ () เรามีคำสั่งการตัดสินเงื่อนไขมากมาย และถ้าเราเพิ่มพิซซ่าใหม่เราต้องเพิ่มการตัดสินแบบมีเงื่อนไขใหม่ แต่เดี๋ยวก่อนโดยใช้โปรเซสเซอร์คำอธิบายประกอบและโหมดโรงงานเราสามารถมีโปรเซสเซอร์คำอธิบายประกอบการสร้างสิ่งเหล่านี้หากคำสั่ง ด้วยวิธีนี้รหัสที่เราต้องการจะเป็นแบบนี้:
PIZZASTORE ชั้นเรียนสาธารณะ {Private Meatfactory Factory = New MealFactory (); คำสั่งอาหารสาธารณะ (String MealName) {return Factory.Create (MealName); } readConsole สตริงส่วนตัว () {Scanner Scanner = สแกนเนอร์ใหม่ (System.in); สตริงมื้อ = scanner.nextline (); Scanner.close (); อาหารกลับ; } โมฆะคงที่สาธารณะหลัก (สตริง [] args) {system.out.println ("ยินดีต้อนรับสู่ Pizza Store"); pizzastore pizzastore = new pizzastore (); มื้ออาหาร = pizzastore.order (readconsole ()); System.out.println ("Bill: $" + Meal.getPrice ()); }} public class mealfactory {public meal create (string id) {ถ้า (id == null) {โยน unlegalargumentException ใหม่ ("id is null!"); } if ("calzone" .equals (id)) {ส่งคืน calzonepizza ใหม่ (); } if ("tiramisu" .equals (id)) {ส่งคืน tiramisu ใหม่ (); } if ("margherita" .equals (id)) {ส่งคืน margheritapizza ใหม่ (); } โยน unleglArgumentException ใหม่ ("unknown id =" + id); -5. @factory คำอธิบายประกอบ
คุณเดาได้ไหมว่าเราวางแผนที่จะใช้โปรเซสเซอร์คำอธิบายประกอบเพื่อสร้างคลาสอาหาร โดยทั่วไปเราต้องการให้คำอธิบายประกอบและโปรเซสเซอร์เพื่อสร้างชั้นเรียนจากโรงงาน
มาดูคำอธิบายประกอบ @factory:
@Target (ElementType.type) @retention (RetentionPolicy.Class) โรงงานสาธารณะ @Interface { / ** * ชื่อของโรงงาน * / คลาส <?> type (); / ** * ตัวระบุสำหรับการพิจารณาว่ารายการใดควรเป็นอินสแตนซ์ */ string id ();}แนวคิดมีดังนี้: เราใส่คำอธิบายประกอบคลาสอาหารเหล่านั้นใช้ประเภท () เพื่อระบุว่าโรงงานคลาสนี้เป็นของและใช้ ID () เพื่อระบุประเภทเฉพาะของคลาสนี้ มาใช้คำอธิบายประกอบ @factory กับคลาสเหล่านี้กันเถอะ:
@Factory (type = Margheritapizza.class, id = "Margherita") คลาสสาธารณะ Margheritapizza ใช้อาหาร {@Override Float Public Float GetPrice () {return 6.0f; }} @Factory (type = calzonepizza.class, id = "calzone") คลาสสาธารณะ calzonepizza ใช้อาหาร {@Override public float getPrice () {return 8.5f; }} @Factory (type = tiramisu.class, id = "tiramisu") คลาสสาธารณะ Tiramisu ใช้อาหาร {@Override public Float getPrice () {return 4.5f; -คุณอาจถามว่าเราสามารถใช้คำอธิบายประกอบ @factory กับอินเทอร์เฟซมื้ออาหารได้หรือไม่? คำตอบคือไม่เพราะคำอธิบายประกอบไม่สามารถสืบทอดได้ นั่นคือถ้ามีคำอธิบายประกอบในคลาส X, คลาส Y ขยาย X ดังนั้นคลาส Y จะไม่ได้รับการอธิบายประกอบในคลาส X ก่อนที่เราจะเขียนโปรเซสเซอร์เราต้องชี้แจงกฎสองสามข้อ:
ตัวประมวลผลคำอธิบายประกอบ:
Public Class FactoryProcessor ขยาย AbstractProcessor {ประเภทส่วนตัว TypeUtils; องค์ประกอบเอกชน Elementutils; ฟิลเลอร์ฟิลเลอร์ส่วนตัว; Messager ส่วนตัว; แผนที่ส่วนตัว <String, FactoryGroupedClasses> FactoryClasses = ใหม่ LinkedHashMap <String, FactoryGroupedClasses> (); @Override เป็นโมฆะที่ซิงโครไนซ์สาธารณะ init (ประมวลผลการประมวลผลสิ่งแวดล้อม) {super.init (processingEnv); typeUtils = processingenv.getTypeutils (); elementUtils = processingenv.getElementUtils (); filer = processingenv.getFiler (); messager = processingenv.getMessager (); } @Override กระบวนการบูลีนสาธารณะ (set <? ขยายการพิมพ์> arg0, roundenvironment arg1) {... กลับเท็จ; } @Override ชุดสาธารณะ <String> getSupportedAntationTypes () {set <String> annotataions = ใหม่ linkedHashSet <String> (); Annotataions.add (Factory.class.getCanonicalName ()); กลับ annotataions; } @Override Public SourceVersion GetSupportedSourceVersion () {return sourceVersion.latestsupported (); - ในวิธีการ getSupportedAnnotationTypes() เราระบุว่า @factory คำอธิบายประกอบจะถูกประมวลผลโดยโปรเซสเซอร์นี้
สรุป
ข้างต้นเป็นเนื้อหาทั้งหมดของบทความนี้ ฉันหวังว่าเนื้อหาของบทความนี้จะมีค่าอ้างอิงบางอย่างสำหรับการศึกษาหรือที่ทำงานของทุกคน หากคุณมีคำถามใด ๆ คุณสามารถฝากข้อความไว้เพื่อสื่อสาร ขอบคุณสำหรับการสนับสนุน Wulin.com