Java支持注解形式,合理使用注解,可以對我們的編程提供極大的便利。JAVA自身提供了三種注解,分別是:@Override,@Deprecated,@SuppreWarnings.大家平時應該看見這個這三個注解,除此之外,我們還可以自定義注解。對于自定義的注解,可以加上自己的處理邏輯,這樣在某些場合,我們就可以用注解標示某些類或這個方法,這樣即可做到不侵入類或者方法內部修改代碼,就可以完成我們指定的功能,很是方便。
一、 JAVA自定義注解
@interface MyAnnotation{}
一個最基本的Java自定義注解形式如上所示,但是,有一些點需要注意下:
1、注解內方法不能拋出任何異常;
2、方法應該返回以下之一:基本數(shù)據(jù)類型, 字符串, 類, 枚舉或這些類型的數(shù)組形式.
3、注解內方法不能有任何參數(shù);
4、必須使用@interface來定義其為一個注解;
5、注解內方法方法可以設置一個默認值;
1.1、注解類型
主要有三種類型的注解:

1)Marker Annotation
注解沒有任何方法,就稱為Marker annotation,比如:
@interface MyAnnotation{}
比如java中的@Override 和@Deprecated 注解就是 marker annotation.
2) Single-Value Annotaion
只有一個方法的注解就稱為Annotation,比如:
@interface MyAnnotation{
int value();
}
我們可以提供默認值,比如:
@interface MyAnnotation{
int value() default 0;
}
應用這個注解示例如下:
@MyAnnotation(value=10)
其中value可以設置為任何int數(shù)值。
3)Multi-Value Annotation
如果一個注解有超過一個方法,那么就稱為Multi-Value annotation。比如:
@interface MyAnnotation{
int value1();
String value2();
String value3();
}
我們可以為注解的方法提供默認值,比如:
@interface MyAnnotation{
int value1() default 1;
String value2() default "";
String value3() default "xyz";
}
應用這個注解示例如下:
@MyAnnotation(value1=10,value2="Arun Kumar",value3="Ghaziabad")
1.2、Java元注解
Java有四種元注解,元注解專職負責注解其他的注解:
@Target
@Target表示該注解可以用于什么地方,它的ElementType參數(shù)包括
| Element | 應用對象 |
|---|---|
| TYPE | class, interface or enumeration |
| FIELD | fields |
| METHOD | methods |
| CONSTRUCTOR | constructors |
| LOCAL_VARIABLE | local variables |
| ANNOTATION_TYPE | annotation type |
| PARAMETER | parameter |
例如我們的注解可以應用在類上,那么定義如下:
@Target(ElementType.TYPE)
@interface MyAnnotation{
int value1();
String value2();
}
@Retention
@Retention表示需要在什么級別保存該注解信息。可選的RententionPolicy參數(shù)
| RetentionPolicy | Availability |
|---|---|
| RetentionPolicy.SOURCE | refers to the source code, discarded during compilation. It will not be available in the compiled class. |
| RetentionPolicy.CLASS | refers to the .class file, available to java compiler but not to JVM . It is included in the class file. |
| RetentionPolicy.RUNTIME | refers to the runtime, available to java compiler and JVM . |
舉個RetentionPolicy.RUNTIME的注解例子:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface MyAnnotation{
int value1();
String value2();
}
@Inherited
默認情況下, 注解是不能被自類繼承的,加上@Inherited后,則允許子類繼承父類中的注解。
@Inherited
@interface ForEveryone { }//Now it will be available to subclass also
@interface ForEveryone { }
class Superclass{}
class Subclass extends Superclass{}
@Documented
@Documented表示將此注解包含在javadoc中。
1.3、Demo示例
舉個簡單的從創(chuàng)建到應用的完整例子:
//Creating annotation
import java.lang.annotation.*;
import java.lang.reflect.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface MyAnnotation{
int value();
}
//Applying annotation
class Hello{
@MyAnnotation(value=10)
public void sayHello(){
System.out.println("hello annotation");
}
}
//Accessing annotation
class TestCustomAnnotation1{
public static void main(String args[])throws Exception{
Hello h=new Hello();
Method m=h.getClass().getMethod("sayHello");
MyAnnotation manno=m.getAnnotation(MyAnnotation.class);
System.out.println("value is: "+manno.value());
}
}
程序最重輸出:value is: 10
二、注解應用(切片)
定義好注解后,重點是我們怎么靈活的應用注解。我先舉個我自定義注解的應用場景,我的接口需要權限校驗,具體流程為:方法傳進去三個參數(shù):資源,操作,資源ID,然后調用權限校驗方法校驗我的權限。
按照常規(guī)方法,那么在每個需要權限校驗的接口處,就需要添加權限校驗的代碼,這樣每次都要侵入方法內部添加代碼,很是不方便,這時,我們就可以自定義一個注解了!
2.1 自定義注解切片應用
2.1.1、我的自定義注解:
我的注解定義如下:
/**
* Permission annotation, check if user has target permission, the permission is composed of "resource:operate:idKey".
*
* <p>
* The target permission tuple is "resource:operate:idKey". For
* convenience, the 'idkey' params can be id or the key of id,if the 'idkey' params value can parse into Integer type, it is id,
* otherwise,it is the key of id, we should retrieve id by the key.
* </p>
*
* <pre>
* "P:CREATE:1", it is a permission tuple,and the param of 'idKey' is id.
* "P:CREATE:ProductId", it is a permission tuple, and the param of 'idKey' is the key of id ,so we should retrieve id by the key .
* </pre>
*
* @author pioneeryi
* @since 2019-07-30 19:20
**/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Mapping
@Documented
public @interface HasPermission {
String resource();
String operate();
String idKey();
}
2.1.2、我的切片邏輯
為了應用這個注解,我需要對每個加了注解的方法進行切片:
@Aspect
public class AuthorizeAspect {
private Logger logger = LoggerFactory.getLogger(AuthzProcessor.class);
AuthzProcessor authzProcess = new AuthzProcessor();
@Pointcut("execution(* *(..))&&@annotation(com.tencent.dcf.authz.annotation.HasPermission)")
public void authorizePointCut() {
}
@Around("authorizePointCut()")
public Object permissionCheck(ProceedingJoinPoint joinPoint) throws Throwable {
if (!authzProcess.before(joinPoint)) {
return authzProcess.aheadReturn(joinPoint);
}
Object result = joinPoint.proceed();
return result;
}
}
通過如上切片,即可對每個加了@HasPermission的方法進行切片,切片后,處理邏輯如下:
public class AuthzProcessor implements AopProcessor {
private Logger logger = LoggerFactory.getLogger(AuthzProcessor.class);
private ShiroAuthorize authorize = new ShiroAuthorize();
//default set auth enable
private boolean authEnable = true;
@Override
public void enable(boolean enable) {
this.authEnable = enable;
}
@Override
public boolean before(ProceedingJoinPoint joinPoint) {
if (!authEnable) {
return true;
}
Permission permission = getHasPermission(joinPoint);
try {
return authorize.hasPermission(permission.getResource(), permission.getOperate(), permission.getResourceId());
} catch (Exception exception) {
logger.warn("user authorize occurs exception {}", exception.getMessage());
}
return false;
}
@Override
public Object aheadReturn(ProceedingJoinPoint joinPoint) {
throw new DcfAuthorizationException("Authorize failed");
}
@Override
public void after(ProceedingJoinPoint joinPoint, Object result, long startTime) {
}
private Permission getHasPermission(ProceedingJoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
HasPermission hasPermission = method.getAnnotation(HasPermission.class);
if (hasPermission == null) {
throw new RuntimeException("Horizon annotation is null");
}
String resource = hasPermission.resource();
String operate = hasPermission.operate();
String resourceId = getResourceId(hasPermission, joinPoint);
Permission permission = new Permission(resource, operate, resourceId);
return permission;
}
}
其中,我們在getHasPermission中獲取注解的所有值,然后進行權限校驗。
2.1.3、自定義注解使用
舉一個我的自定義注解應用例子:
@CommandHandler
@HasPermission(resouce="P",operate="create",idKey="productId")
public void handle(CreateProductCommand command) throws ModifyRequirementException, AuthorizationException {
...
}
其中@CommandHandler是AXON框架的注解,因為我們項目均采用領域驅動模式,對領域驅動有興趣,可以看我的另一篇關于領域驅動的文章。此時,我們只需要一個注解即可搞定權限校驗。
切片生效的方法
切片如何生效,遇到一些坑,這里記錄下,如何讓切片生效的方法。
方法一:在maven中加入如下plugin(注意版本)
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.9</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<verbose>true</verbose>
<complianceLevel>1.8</complianceLevel>
<showWeaveInfo>true</showWeaveInfo>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
方法二:Spring項目中,注入外部定義好的切片Bean.
@Configuration
@EnableAspectJAutoProxy
public class AspectConfig {
@Bean
public AuthorizeAspect authorizeAspect() {
return new AuthorizeAspect();
}
}
方法三:Spring項目中,定義切片時加上@Component
@Aspect
@Component
public class AuthorizeAspect {
}
三、總結
本文主要是總結了一下如何自定義注解,以及如何切片注解。并舉了我自己的完整例子完整闡述從定義注解,到切片,以及應用主機的整個過程。
祝工作順利, 天天開心!