JAVA自定義注解并切片應用

Java支持注解形式,合理使用注解,可以對我們的編程提供極大的便利。JAVA自身提供了三種注解,分別是:@Override,@Deprecated,@SuppreWarnings.大家平時應該看見這個這三個注解,除此之外,我們還可以自定義注解。對于自定義的注解,可以加上自己的處理邏輯,這樣在某些場合,我們就可以用注解標示某些類或這個方法,這樣即可做到不侵入類或者方法內部修改代碼,就可以完成我們指定的功能,很是方便。

一、 JAVA自定義注解

@interface MyAnnotation{}

一個最基本的Java自定義注解形式如上所示,但是,有一些點需要注意下:
1、注解內方法不能拋出任何異常;

2、方法應該返回以下之一:基本數(shù)據(jù)類型, 字符串, 類, 枚舉或這些類型的數(shù)組形式.

3、注解內方法不能有任何參數(shù);

4、必須使用@interface來定義其為一個注解;

5、注解內方法方法可以設置一個默認值;

1.1、注解類型

主要有三種類型的注解:


java-annotation-types.jpg

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 {
}

三、總結

本文主要是總結了一下如何自定義注解,以及如何切片注解。并舉了我自己的完整例子完整闡述從定義注解,到切片,以及應用主機的整個過程。

祝工作順利, 天天開心!

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容