文章來源:
一:Java注解簡介
開發中經常使用到注解,在項目中也偶爾會見到過自定義注解,今天就來探討一下這個注解是什么鬼,以及注解的應用場景和如何自定義注解。
下面列舉開發中常見的注解
- @Override:用於標識該方法繼承自超類,當父類的方法被刪除或修改了,編譯器會提示錯誤信息(我們最經常看到的toString()方法上總能看到這貨)
- @Deprecated:表示該類或者該方法已經不推薦使用,已經過期了,如果用戶還是要使用,會生成編譯的警告
- @SuppressWarnings:用於忽略的編譯器警告信息
- Junit測試:@Test
- Spring的一些注解:@Controller、@RequestMapping、@RequestParam、@ResponseBody、@Service、@Component、@Repository、@Resource、@Autowire
- Java驗證的注解:@NotNull、@Email
下面看一下注解Override.java的廬山真面目:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override { }
二:Java注解基本知識
1 Java注解數據類型
注解是寫在.java文件中,使用@interface作為關鍵字, 所以注解也是Java的一種數據類型,從廣泛的定義來說,Class、Interface、Enum、Annotation都屬於Class類型。
2 Java元注解
在創建注解的時候,需要使用一些注解來描述自己創建的注解,就是寫在@interface上面的那些注解,這些注解被稱為元注解,如在Override中看到的@Target、@Retention等。下面列出一些元注解
@Documented: 用於標記在生成javadoc時是否將注解包含進去,可以看到這個注解和@Override一樣,注解中空空如也,什么東西都沒有
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Documented { }
@Target:用於定義注解可以在什么地方使用,默認可以在任何地方使用,也可以指定使用的范圍,開發中將注解用在類上(如@Controller)、字段上(如@Autowire)、方法上(如@RequestMapping)、方法的參數上(如@RequestParam)等比較常見。
TYPE : 類、接口或enum聲明 FIELD: 域(屬性)聲明 METHOD: 方法聲明 PARAMETER: 參數聲明 CONSTRUCTOR: 構造方法聲明 LOCAL_VARIABLE:局部變量聲明 ANNOTATION_TYPE:注釋類型聲明 PACKAGE: 包聲明
Target.java
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Target { /** * Returns an array of the kinds of elements an annotation type * can be applied to. * @return an array of the kinds of elements an annotation type * can be applied to */ ElementType[] value(); }
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 */ TYPE_PARAMETER, /** Use of a type */ TYPE_USE }
@Inherited:允許子類繼承父類中的注解,可以通過反射獲取到父類的注解
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Inherited { }
@Constraint:用於校驗屬性值是否合法
@Documented @Target({ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface Constraint { Class<? extends ConstraintValidator<?, ?>>[] validatedBy(); }
@Retention:注解的聲明周期,用於定義注解的存活階段,可以存活在源碼級別、編譯級別(字節碼級別)、運行時級別
SOURCE:源碼級別,注解只存在源碼中,一般用於和編譯器交互,用於檢測代碼。如@Override, @SuppressWarings。
CLASS:字節碼級別,注解存在於源碼和字節碼文件中,主要用於編譯時生成額外的文件,如XML,Java文件等,但運行時無法獲得。 如mybatis生成實體和映射文件,這個級別需要添加JVM加載時候的代理(javaagent),使用代理來動態修改字節碼文件。
RUNTIME:運行時級別,注解存在於源碼、字節碼、java虛擬機中,主要用於運行時,可以使用反射獲取相關的信息。
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Retention { /** * Returns the retention policy. * @return the retention policy */ RetentionPolicy value(); }
3 Java注解的內容
在上面的注解源碼中可以看到有的注解中沒有任何內容,有的注解的有內容,看似像方法。
注解的內容的語法格式: 數據類型 屬性名() default 默認值,數據類型用於描述屬性的數據類型,默認值是說當沒有給屬性賦值時使用默認值,一般String使用空字符串”“作為默認值,數組一般使用空數組{ }作為默認值.
下面看一下SpringMVC中的RequestMapping的注解的聲明
@Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Mapping public @interface RequestMapping { String name() default ""; @AliasFor("path") String[] value() default {}; @AliasFor("value") String[] path() default {}; RequestMethod[] method() default {}; String[] params() default {}; String[] headers() default {}; String[] consumes() default {}; String[] produces() default {}; }
使用SpringMVC中的RequestMapping注解
@RequestMapping(value = "/list", method = RequestMethod.POST, produces = {"application/json;charset=UTF-8;"}) public String list(){ }
4 注解的使用場景
可以通過注解的聲明周期來分析注解的使用場景:
- SOURCE源碼級別:給編譯器使用,如@Override、@Deprecated 等, 這部分開發者應該使用的場景不多
- CLASS:字節碼級別,這部分也很少見到
- RUNTIME:運行時級別,這個是最多的,幾乎開發者使用到的注解都是運行時級別,運行時注解常用的有以下幾種情況 :
注解中沒有任何屬性的,空的注解,這部分注解通常起到一個標注的作用,如@Test、@Before、@After,通過獲取這些標記注解在邏輯上做一些特殊的處理
可以使用約束注解@Constraint來對屬性值進行校驗,如@Email, @NotNull等
可以通過在注解中使用屬性來配置一些參數,然后可以使用反射獲取這些參數,這些注解沒有其他特殊的功能,只是簡單的代替xml配置的方式來配置一些參數。使用注解來配置參數這在Springboot中得到了熱捧,如@Configuration
關於配置方式xml vs annotation, 一般使用xml配置一些和業務關系不太緊密的配置,使用注解配置一些和業務密切相關的參數。
三:Java注解和反射基本API
// 獲取某個類型的注解 public <A extends Annotation> A getAnnotation(Class<A> annotationClass); // 獲取所有注解(包括父類中被Inherited修飾的注解) public Annotation[] getAnnotations(); // 獲取聲明的注解(但是不包括父類中被Inherited修飾的注解) public Annotation[] getDeclaredAnnotations(); // 判斷某個對象上是否被某個注解進行標注 public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) // 獲取某個類聲明的所有字段 public Field[] getDeclaredFields() throws SecurityException; // 獲取某個方法 public Method getMethod(String name, Class<?>... parameterTypes);
四:自定義注解
使用自定義注解+攔截器或者是AOP等可以進行權限的控制。
下面通過定義一個注解用來限制當用戶訪問接口時必須要登錄的示例
步驟一:定義注解 :RequiresLogin.java
@Documented @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface RequiresLogin { }
步驟二:使用注解
@Controller @RequestMapping("/user") public class UserController { @RequiresLogin @RequestMapping(value = "/list", produces = {"application/json;charset=UTF-8;"}) public String getUserList(){ System.out.println("--------------"); return "[{'id': 1, 'username':'zhangsan'}]"; } }
步驟三:使用AOP進行攔截,解析注解
public class LoginAdvices { public void before(JoinPoint joinPoint) throws Exception{ Object target = joinPoint.getTarget(); String methodName = joinPoint.getSignature().getName(); System.out.println(target + "-------" + methodName); Method method = target.getClass().getMethod(methodName); boolean annotationPresent = method.isAnnotationPresent(RequiresLogin.class); if (annotationPresent) { // 用戶必須登錄 boolean isLogin = false; if (!isLogin) { throw new Exception("訪問該接口必須先登錄"); } else { System.out.println("已登錄..."); } } } }
在applicationContext.xml中配置aop
<bean id="loginAdvices" class="com.mengdee.manager.aop.LoginAdvices"/> <!-- aop配置 --> <aop:config proxy-target-class="true"> <!--切面 --> <aop:aspect ref="loginAdvices"> <!-- 切點 --> <aop:pointcut id="pointcut1" expression="execution(* com.mengdee.manager.controller.*.*(..))"/> <!--連接通知方法與切點 --> <aop:before method="before" pointcut-ref="pointcut1"/> </aop:aspect> </aop:config>
自定義異常
為什么要自定義異常 :Java雖然提供了豐富的異常處理類,但是在項目中還會經常使用自定義異常,其主要原因是Java提供的異常類在某些情況下還是不能滿足各種業務的需求。 例如系統中有些錯誤是符合Java語法,但不符合業務邏輯。如當用戶登錄時賬號不存在或者賬號已鎖定可以自定義一個賬號異常AccountException。 或者有些情況下Java的同一個異常可能會有多種原因引起,在排查問題時不容易定位錯誤,此時可以使用自定義一個更加明確的異常。
自定義異常的好處:自定義異常可以使異常更加明確,可以隱藏底層的異常,這樣更安全,異常信息更加直觀。
自定義異常的使用:自定義異常一般繼承自Exception或者RuntimeException,根據業務需要可以帶一些屬性作為構造函數的參數,自定義異常需要程序員手動拋出異常,並處理異常。
下面是Apache Shiro中自定義異常的示例:
public class ShiroException extends RuntimeException { public ShiroException() { } public ShiroException(String message) { super(message); } public ShiroException(Throwable cause) { super(cause); } public ShiroException(String message, Throwable cause) { super(message, cause); } }