首先一句話結論:注解就是一種通過在類、方法、或者屬性等上使用類似@xxx的方式進行“打标簽”,然後可以通過反射機制對标簽的内容進行解析并進行相應處理的手段。
注解是java中的一個重要知識點,從java5後開始引入,尤其在spring架構中大量使用。比較常用的有@controller、@service等等各種,本文将從注解的實作原理出發,通過一些demo代碼的實作,進行分析。
一、 注解定義方式
直接上代碼,看看spring中@Service注解的定義就知道了:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {
String value() default "";
}
可以看到注解的定義和接口定義很像,但是多了@字元,注解的定義上有以下約定:
1.隻能定義屬性名,不能定義方法
2.屬性的可見性隻有public和default,不寫則預設後者
3.屬性的類型隻能支援:基本資料類型、string、class、enum、Annotation類型及以上類型的數組
4.可以加上defult關鍵字指明預設值,當某字段不指明預設值時,必須在進行注解标注的時候進行此字段值的指定。
5.當使用value作為屬性名稱時,可以不顯式指定value=“xxx”,如可以直接使用@Service(“xxxService”)
二、元注解
所謂元注解就是java中預設實作的專門對注解進行注解的注解。元注解的總數就5個,下面我們以上面講到的@Service注解為例子各個擊破:
[email protected]
此注解用于表示目前注解的使用範圍,@Target({ElementType.TYPE})就代表着@Service這個注解是專門用來注解到類、接口、或者枚舉類型上面的,當在方法上面加這個注解時,就會報錯。可以看到注解位置是一個枚舉類型,完整定義如下
public enum ElementType {
/** Class, interface (including annotation type), or enum declaration */
TYPE,
/** Field declaration (includes enum constants) */
FIELD,
/** Method declaration */
METHOD,
/** Formal parameter declaration */
PARAMETER,
/** Constructor declaration */
CONSTRUCTOR,
/** Local variable declaration */
LOCAL_VARIABLE,
/** Annotation type declaration */
ANNOTATION_TYPE,
/** Package declaration */
PACKAGE,
/**
* Type parameter declaration
*
* @since 1.8
*/
TYPE_PARAMETER,
/**
* Use of a type
*
* @since 1.8
*/
TYPE_USE
}
[email protected]
此注解用于表示目前注解的生命周期,說人話就是這個注解作用會保留到什麼時候,如@Retention(RetentionPolicy.RUNTIME)就表示在程式運作期間依然有效,此時就可以通過反射拿到注解的資訊,完整的枚舉定義如下
public enum RetentionPolicy {
/**
* Annotations are to be discarded by the compiler.
*/
SOURCE,
/**
* Annotations are to be recorded in the class file by the compiler
* but need not be retained by the VM at run time. This is the default
* behavior.
*/
CLASS,
/**
* Annotations are to be recorded in the class file by the compiler and
* retained by the VM at run time, so they may be read reflectively.
*
* @see java.lang.reflect.AnnotatedElement
*/
RUNTIME
}
[email protected]
當被此注解所注解時,使用javadoc工具生成文檔就會帶有注解資訊。
[email protected]
此注解與繼承有關,當A注解添加此注解後,将A注解添加到某類上,此類的子類就會繼承A注解。
@Inherited
public @interface A{
}
@A
public class Parent{}
public class Son entends Parant{}//Son類繼承了父類的A注解
[email protected]
此注解顧名思義是擁有可以重複注解的能力。想象這樣一個場景,我們需要定時執行某個任務,需要在每周一和周三執行,并且這個時間是可以靈活調整的,此時這個元注解就能派上用場:
@Repeatable(Schedules.class)
public @interface Schedule {
String date();
}
public @interface Schedules {
Schedule[] value();
}
@Schedule(date = "周一")
@Schedule(date = "周三")
public class Executor {
}
注意看到此元注解後面括号裡内容,在這指定的類叫做容器注解,意思是儲存這多個注解的容器,故我們建立一個@Schedules注解作為@Schedule的容器注解,容器注解必須含有一個名字為value,傳回類型為需放入此容器的注解數組的屬性。
三、自定義實作一個注解
下面我們以web項目中非常常見的鑒權場景為例自己實作一個自定義注解。
首先我們定義系統的使用人員身份,有超級管理者、管理者、訪客三種身份。
public enum IdentityEnums {
SUPER_ADMIN,
ADMIN,
VISIVOR
}
接下來我們定義一個權限注解:
@Target({ElementType.TYPE,ElementType.METHOD})
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface Authorization {
IdentityEnums[] value();
}
然後使用攔截器的方式,對所有頁面進行統一的鑒權管理,此處隻展示一些關鍵代碼:
public class AuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (handler instanceof HandlerMethod)
{
IdentityEnums user = getIdentityFromRequset(request);//這裡從request裡擷取賬号資訊并判斷身份,自己實作
Authorization auth =((HandlerMethod) handler).getMethodAnnotation(Authorization.class);//擷取方法上面的注解
if (!Arrays.asList(auth.value()).contains(user)){
return false;
}
}
return true;
}
}
最後在spring配置檔案中對攔截器進行配置開啟攔截器:
<!-- 攔截器 -->
<mvc:interceptors>
<mvc:interceptor>
<!-- 比對的是url路徑, 如果不配置或/**,将攔截所有的Controller -->
<mvc:mapping path="/**" />
<!-- 攔截器類 -->
<bean
class="com.xx.xx.AuthInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
在實際使用中,我們将在方法上面添加此自定義注解,當身份權限符合時,才能對頁面進行通路,使用方式如下:
@ResponseBody
@RequestMapping(value = "/management")
@Authorization({IdentityEnums.ADMIN,IdentityEnums.SUPER_ADMIN})
public String management(HttpServletRequest request, HttpServletResponse response)
{
log.info("has permission!");
}