JAVA自定义注解并切片应用

Java支持注解形式,合理使用注解,可以对我们的编程提供极大的便利。JAVA自身提供了三种注解,分别是:@Override,@Deprecated,@SuppreWarnings.大家平时应该看见这个这三个注解,除此之外,我们还可以自定义注解。对于自定义的注解,可以加上自己的处理逻辑,这样在某些场合,我们就可以用注解标示某些类或这个方法,这样即可做到不侵入类或者方法内部修改代码,就可以完成我们指定的功能,很是方便。

一、 JAVA自定义注解

@interface MyAnnotation{}

一个最基本的Java自定义注解形式如上所示,但是,有一些点需要注意下:
1、注解内方法不能抛出任何异常;

2、方法应该返回以下之一:基本数据类型, 字符串, 类, 枚举或这些类型的数组形式.

3、注解内方法不能有任何参数;

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数值。

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参数包括

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参数

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示例

举个简单的从创建到应用的完整例子:

//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

二、注解应用(切片)

定义好注解后,重点是我们怎么灵活的应用注解。我先举个我自定义注解的应用场景,我的接口需要权限校验,具体流程为:方法传进去三个参数:资源,操作,资源ID,然后调用权限校验方法校验我的权限。

按照常规方法,那么在每个需要权限校验的接口处,就需要添加权限校验的代码,这样每次都要侵入方法内部添加代码,很是不方便,这时,我们就可以自定义一个注解了!

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

三、总结

本文主要是总结了一下如何自定义注解,以及如何切片注解。并举了我自己的完整例子完整阐述从定义注解,到切片,以及应用主机的整个过程。

祝工作顺利, 天天开心!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容