開(kāi)發(fā)中經(jīng)常使用到注解,在項(xiàng)目中也偶爾會(huì)見(jiàn)到過(guò)自定義注解,今天就來(lái)探討一下這個(gè)注解是什么鬼,以及注解的應(yīng)用場(chǎng)景和如何自定義注解。
下面列舉開(kāi)發(fā)中常見(jiàn)的注解
@Override:用于標(biāo)識(shí)該方法繼承自超類, 當(dāng)父類的方法被刪除或修改了,編譯器會(huì)提示錯(cuò)誤信息(我們最經(jīng)常看到的toString()方法上總能看到這貨)
@Deprecated:表示該類或者該方法已經(jīng)不推薦使用,已經(jīng)過(guò)期了,如果用戶還是要使用,會(huì)生成編譯的警告
@SuppressWarnings:用于忽略的編譯器警告信息
Junit測(cè)試:@Test
Spring的一些注解:@Controller、@RequestMapping、@RequestParam、@ResponseBody、@Service、@Component、@Repository、@Resource、@Autowire
Java驗(yàn)證的注解:@NotNull、@Email
下面看一下注解Override.java的廬山真面目
1
2
3
4
5
|
@Target (ElementType.METHOD) @Retention (RetentionPolicy.SOURCE) public @interface Override { } |
二:Java注解基本知識(shí)
1. Java注解數(shù)據(jù)類型
注解是寫在.java文件中,使用@interface作為關(guān)鍵字, 所以注解也是Java的一種數(shù)據(jù)類型,從廣泛的定義來(lái)說(shuō),Class、Interface、Enum、Annotation都屬于Class類型。
2. Java元注解
在創(chuàng)建注解的時(shí)候,需要使用一些注解來(lái)描述自己創(chuàng)建的注解,就是寫在@interface上面的那些注解,這些注解被稱為元注解,如在Override中看到的@Target、@Retention等。下面列出一些元注解
@Documented: 用于標(biāo)記在生成javadoc時(shí)是否將注解包含進(jìn)去,可以看到這個(gè)注解和@Override一樣,注解中空空如也,什么東西都沒(méi)有
1
2
3
4
5
6
|
@Documented @Retention (RetentionPolicy.RUNTIME) @Target (ElementType.ANNOTATION_TYPE) public @interface Documented { } |
@Target:用于定義注解可以在什么地方使用,默認(rèn)可以在任何地方使用,也可以指定使用的范圍,開(kāi)發(fā)中將注解用在類上(如@Controller)、字段上(如@Autowire)、方法上(如@RequestMapping)、方法的參數(shù)上(如@RequestParam)等比較常見(jiàn)。
TYPE : 類、接口或enum聲明
FIELD: 域(屬性)聲明
METHOD: 方法聲明
PARAMETER: 參數(shù)聲明
CONSTRUCTOR: 構(gòu)造方法聲明
LOCAL_VARIABLE:局部變量聲明
ANNOTATION_TYPE:注釋類型聲明
PACKAGE: 包聲明
Target.java
1
2
3
4
5
6
7
8
9
10
11
12
|
@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(); } |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
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:允許子類繼承父類中的注解,可以通過(guò)反射獲取到父類的注解
1
2
3
4
5
6
|
@Documented @Retention (RetentionPolicy.RUNTIME) @Target (ElementType.ANNOTATION_TYPE) public @interface Inherited { } |
@Constraint:用于校驗(yàn)屬性值是否合法
1
2
3
4
5
6
|
@Documented @Target ({ElementType.ANNOTATION_TYPE}) @Retention (RetentionPolicy.RUNTIME) public @interface Constraint { Class<? extends ConstraintValidator<?, ?>>[] validatedBy(); } |
@Retention:注解的聲明周期,用于定義注解的存活階段,可以存活在源碼級(jí)別、編譯級(jí)別(字節(jié)碼級(jí)別)、運(yùn)行時(shí)級(jí)別
SOURCE:源碼級(jí)別,注解只存在源碼中,一般用于和編譯器交互,用于檢測(cè)代碼。如@Override, @SuppressWarings。
CLASS:字節(jié)碼級(jí)別,注解存在于源碼和字節(jié)碼文件中,主要用于編譯時(shí)生成額外的文件,如XML,Java文件等,但運(yùn)行時(shí)無(wú)法獲得。 如mybatis生成實(shí)體和映射文件,這個(gè)級(jí)別需要添加JVM加載時(shí)候的代理(javaagent),使用代理來(lái)動(dòng)態(tài)修改字節(jié)碼文件。
RUNTIME:運(yùn)行時(shí)級(jí)別,注解存在于源碼、字節(jié)碼、java虛擬機(jī)中,主要用于運(yùn)行時(shí),可以使用反射獲取相關(guān)的信息。
1
2
3
4
5
6
7
8
9
10
|
@Documented @Retention (RetentionPolicy.RUNTIME) @Target (ElementType.ANNOTATION_TYPE) public @interface Retention { /** * Returns the retention policy. * @return the retention policy */ RetentionPolicy value(); } |
3. Java注解的內(nèi)容
在上面的注解源碼中可以看到有的注解中沒(méi)有任何內(nèi)容,有的注解的有內(nèi)容,看似像方法。
注解的內(nèi)容的語(yǔ)法格式: 數(shù)據(jù)類型 屬性名() default 默認(rèn)值,數(shù)據(jù)類型用于描述屬性的數(shù)據(jù)類型,默認(rèn)值是說(shuō)當(dāng)沒(méi)有給屬性賦值時(shí)使用默認(rèn)值,一般String使用空字符串”“作為默認(rèn)值,數(shù)組一般使用空數(shù)組{ }作為默認(rèn)值.
下面看一下SpringMVC中的RequestMapping的注解的聲明
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
@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注解
1
2
3
4
5
6
|
@RequestMapping (value = "/list" , method = RequestMethod.POST, produces = { "application/json;charset=UTF-8;" }) public String list(){ } |
4. 注解的使用場(chǎng)景
可以通過(guò)注解的聲明周期來(lái)分析注解的使用場(chǎng)景:
SOURCE源碼級(jí)別:給編譯器使用,如@Override、@Deprecated 等, 這部分開(kāi)發(fā)者應(yīng)該使用的場(chǎng)景不多
CLASS:字節(jié)碼級(jí)別,這部分也很少見(jiàn)到
RUNTIME:運(yùn)行時(shí)級(jí)別,這個(gè)是最多的,幾乎開(kāi)發(fā)者使用到的注解都是運(yùn)行時(shí)級(jí)別,運(yùn)行時(shí)注解常用的有以下幾種情況
注解中沒(méi)有任何屬性的,空的注解,這部分注解通常起到一個(gè)標(biāo)注的作用,如@Test、@Before、@After,通過(guò)獲取這些標(biāo)記注解在邏輯上做一些特殊的處理
可以使用約束注解@Constraint來(lái)對(duì)屬性值進(jìn)行校驗(yàn),如@Email, @NotNull等
可以通過(guò)在注解中使用屬性來(lái)配置一些參數(shù),然后可以使用反射獲取這些參數(shù),這些注解沒(méi)有其他特殊的功能,只是簡(jiǎn)單的代替xml配置的方式來(lái)配置一些參數(shù)。使用注解來(lái)配置參數(shù)這在Spring boot中得到了熱捧,如@Configuration
關(guān)于配置方式xml vs annotation, 一般使用xml配置一些和業(yè)務(wù)關(guān)系不太緊密的配置,使用注解配置一些和業(yè)務(wù)密切相關(guān)的參數(shù)。
三:Java注解和反射基本API
1
2
3
4
5
6
7
8
9
10
11
12
13
|
// 獲取某個(gè)類型的注解 public <A extends Annotation> A getAnnotation(Class<A> annotationClass); // 獲取所有注解(包括父類中被Inherited修飾的注解) public Annotation[] getAnnotations(); // 獲取聲明的注解(但是不包括父類中被Inherited修飾的注解) public Annotation[] getDeclaredAnnotations(); // 判斷某個(gè)對(duì)象上是否被某個(gè)注解進(jìn)行標(biāo)注 public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) // 獲取某個(gè)類聲明的所有字段 public Field[] getDeclaredFields() throws SecurityException; // 獲取某個(gè)方法 public Method getMethod(String name, Class<?>... parameterTypes); |
四:自定義注解
使用自定義注解+攔截器或者是AOP等可以進(jìn)行權(quán)限的控制。
下面通過(guò)定義一個(gè)注解用來(lái)限制當(dāng)用戶訪問(wèn)接口時(shí)必須要登錄的示例
步驟一:定義注解
RequiresLogin.java
1
2
3
4
5
6
|
@Documented @Target ({ElementType.METHOD}) @Retention (RetentionPolicy.RUNTIME) public @interface RequiresLogin { } |
步驟二:使用注解
1
2
3
4
5
6
7
8
9
10
11
|
@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進(jìn)行攔截,解析注解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
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( "訪問(wèn)該接口必須先登錄" ); } else { System.out.println( "已登錄..." ); } } } } |
在applicationContext.xml中配置aop
1
2
3
4
5
6
7
8
9
10
11
|
<bean id= "loginAdvices" class = "com.mengdee.manager.aop.LoginAdvices" /> <!-- aop配置 --> <aop:config proxy-target- class = "true" > <!--切面 --> <aop:aspect ref= "loginAdvices" > <!-- 切點(diǎn) --> <aop:pointcut id= "pointcut1" expression= "execution(* com.mengdee.manager.controller.*.*(..))" /> <!--連接通知方法與切點(diǎn) --> <aop:before method= "before" pointcut-ref= "pointcut1" /> </aop:aspect> </aop:config> |
自定義異常
為什么要自定義異常
Java雖然提供了豐富的異常處理類,但是在項(xiàng)目中還會(huì)經(jīng)常使用自定義異常,其主要原因是Java提供的異常類在某些情況下還是不能滿足各種業(yè)務(wù)的需求。 例如系統(tǒng)中有些錯(cuò)誤是符合Java語(yǔ)法,但不符合業(yè)務(wù)邏輯。如當(dāng)用戶登錄時(shí)賬號(hào)不存在或者賬號(hào)已鎖定可以自定義一個(gè)賬號(hào)異常AccountException。
或者有些情況下Java的同一個(gè)異常可能會(huì)有多種原因引起,在排查問(wèn)題時(shí)不容易定位錯(cuò)誤,此時(shí)可以使用自定義一個(gè)更加明確的異常。
自定義異常的好處:自定義異常可以使異常更加明確,可以隱藏底層的異常,這樣更安全,異常信息更加直觀。
自定義異常的使用:自定義異常一般繼承自Exception或者RuntimeException,根據(jù)業(yè)務(wù)需要可以帶一些屬性作為構(gòu)造函數(shù)的參數(shù),自定義異常需要程序員手動(dòng)拋出異常,并處理異常。
下面是Apache Shiro中自定義異常的示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
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); } } |
以上即是關(guān)于Java注解Annotation與自定義注解的詳細(xì)說(shuō)明