Annotation的技術風格為Java 5 之後所推暢出來的新模式,並將註解區分為作用在代碼上及作用在註解上的註解,亦可使用其他框架套件進行處理這些標籤,用於增強或修改程序行為等,另所有開發者所產生的Annotation皆可進行擴展,便於開發者將任何資料或元資料(Metadata)與程式元素(類、方法、成員變數等)進行關聯,而Spring框架的三大核心運作思想為IoC(Inversion of Control,控制反轉)和DI(Dependency Inject,依賴注入)及面向切面的程式設計(Aspect-oriented programming,AOP),故我們這邊先透過原生Java開發一個小程式來達到三大類核心思維,以便讀者先了解原生Java元件的運作概念。
註解本身就是繼承一個@interface的特殊介面接口,處理註解的工具將接收那些實現了這個註解介面的對象,通過java類別反射(Relfection)行為來取得各類別的宣告參數(field)與方法(Method)是否當下有配置註解,並通過getAnnotation方法取得其開發者所配置的註解,其方法最終皆為透過產生一項代理動態物件進行產生調用(invoke)AnnotationInvocationHandler的類別方法,並注入建構子兩項參數,分別為開發者所欲取得的Annotation及memberValues集合參數物件,memberValues 為放置註解成員的屬性名稱和值,該方法會從其中取得memberValues此Map的索引對應值,即可提供給使用者進行各類元數據解析開發。
其片段程式碼為
(Annotation)Proxy.newProxyInstance(type.getClassLoader(), new Class[]{type}, new AnnotationInvocationHandler(type, memberValues));
Java 內置了一些常用的註解,其3項在java.lang中,剩下4個在java.lang.annotation中
代碼性質註解
Annotation name | Descrption |
---|---|
@Override | 在某個父類別(class)或實現的接口(interface)中進行配置該方法,並確保當前類別重寫了該方法。若沒有父類別無該方法,會編譯報錯。 |
@Deprecated | 可修飾的範圍很廣,包括類、方法、參數,他表示對應的代碼已經過時了,開發者不應該再使用它,編譯時會警告。 |
@SuppressWarnings | 表示會忽略編譯過程中的警告 |
作用在註解上的註解
Annotation name | Descrption |
---|---|
@Retention | 標識這個註解怎麼儲存,是只在程式碼中,還是編入class檔案中,或者是在執行時可以通過反射存取。 |
@Documented | 標記這些註解是否包含在使用者文件中 |
@Target | 標記這個註解應該是哪種 Java 成員[AnnotationType,Constructor,Field,Local_Variable,Method,Module,Package,Parameter,Type,Type_Parameter,Type_use] |
@Inherited | 標記這個註解是繼承於哪個註解類別 |
Java 7 開始,額外添加了 3 個註解:
Annotation name | Descrption |
---|---|
@SafeVarargs | Java 7 開始支援,忽略任何使用參數為泛型變數的方法或建構函式呼叫產生的警告。 |
@FunctionalInterface | Java 8 開始支援,標識一個匿名函式或函數式介面。 |
@Repeatable | Java 8 開始支援,標識某註解可以在同一個聲明上使用多次。 |
以下為透過範例程式碼仿照Spring框架殼新概念為出發點進行介紹,提供各位開發者進行參考
@Prejump為小編為此套小框架設計的註解模式,運用來配置欄位宣告值與屬性,並注入上關聯元件的宣告欄位,可以想像成是Spring框架的@Bean註解概念。
@Retention(RetentionPolicy.RUNTIME)
public @interface Prejump {
String value() default "";
String name() default "";
}
ObjectCloneHandler.INVOCATION_HANDLER 為一個Map配置池存放開發者所配置註解模式的暫存位置,可以想像成一個Spring IoC BeanFactory配置池概念,Spring採用CGLib進行切面式編程,小編這邊採用ProxyInvocation進行切面式編程。
public class ObjectCloneHandler implements InvocationHandler {
private static volatile ObjectCloneHandler instance = null;
public static Map<String,Object> INVOCATION_HANDLER = new HashMap<String,Object>();
public static synchronized ObjectCloneHandler getInstance() {
if ( instance == null ) {
instance = new ObjectCloneHandler();
}
return instance;
}
private Object delegate;
public Object bind(Object delegate) {
//透過配置池取得對應元件
this.delegate = ObjectCloneHandler.getInstance().getInvocationHandler().get(delegate.getClass().getName());;
return this.delegate;
}
....
....
}
註冊邏輯片段,配置完後存入Prejump配置池中。
// 將取得註冊後Prejump的註解模式進行存入配置池中,等待開發者進行宣告注入在進行獲取
private void filterAnnotations(String[] classes) {
Arrays.stream(classes).forEach( clazz -> {
try {
......
......
ObjectCloneHandler.getInstance().getInvocationHandler().put(clazz,object);
} catch (IllegalAccessException illegalAccessException) {
System.out.println("Core :: error :: IllegalAccessException ! ");
} catch (ClassNotFoundException classNotFoundException) {
System.out.println("Core :: error :: classNotFoundException ! ");
} catch (InstantiationException instantiationException) {
// System.out.println("Core :: error :: InstantiationException ! ");
}
});
測試片段,進行注入相關元件
public class AnnotationsTest {
private static final double DELTA = 1e-15;
MakePizzaService makePizzaService;
AnimalService animalService;
@Before
public void init() {
ApplicationBoot.getInstance().run(Main.class);
makePizzaService = (MakePizzaService) ObjectCloneHandler.getInstance().bind(new MakePizzaServiceImpl());
animalService = (AnimalService) ObjectCloneHandler.getInstance().bind(new AnimalServiceImpl());
}
...
...
...
@Test
public void testShowMyCompany() {
assertEquals(makePizzaService.showMyCompany(),"YO ! This is WEISTING COMPANY, We have 15.5 pizzas.");
System.out.println("show my company test pass ! ");
}
}
測試結果,可發現我們已將@Prejump字串注入對應的元件,提供開發者進行邏輯分析
Testing started at 12:30 上午 ...
> Task :cleanTest
> Task :compileJava UP-TO-DATE
> Task :processResources UP-TO-DATE
> Task :classes UP-TO-DATE
> Task :compileTestJava UP-TO-DATE
> Task :processTestResources UP-TO-DATE
> Task :testClasses UP-TO-DATE
> Task :test
show my company test pass !
透過小編提供的範例仿造一個Spring框架中,並以此運作方式先行提供給開發者了解註解在Spring框架中的強大功用與運作方式。
Structure Diagram
根據每個類別進行取得宣告參數與宣告方法的清單,並呼叫getAnnotation進行取得自身所配置的Annotation類別,其方法將會依照不同的對應代碼配置的註解取得方式進行呼叫,各類別的宣告參數類型的註解取得來源為從Field類別中的declaredAnnotations方法,各類別中的方法配置註解取得來源為從Executable類別中declaredAnnotations方法,並統一呼叫AnnotationParser類別中的parseAnnotations、parseAnnotations2及annotationForMap取得其使用者所想獲取之註解(Annotation / @interface),其最終呼叫的方法程式碼為下面片段。
public static Annotation annotationForMap(final Class<? extends Annotation> type, final Map<String, Object> memberValues) {
return (Annotation)AccessController.doPrivileged(new PrivilegedAction<Annotation>() {
public Annotation run() {
return (Annotation)Proxy.newProxyInstance(type.getClassLoader(), new Class[]{type}, new AnnotationInvocationHandler(type, memberValues));
}
});
}
當每個類別的元數據已充分載入,會將此類別暫存在一個容器中,當開發人員要產生一個實體(Entity)時,我們會將預先產生的實體分配給此使用程序,已達到各類物件依賴性關係載入概念,此時便可依據不同的註解(Annotation)映射出不相同的實體類別。
Weisting's Annotation Romp source
Get All Classes Within A Package