JAVA自定义注解并切片应用

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

一、 JAVA自定义注解

@interface MyAnnotation{}

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

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

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

4、必须使用@interface来定义其为一个注解;

5、注解内方法方法可以设置一个默认值;

1.1、注解类型

主要有三种类型的注解:


JAVA自定义注解并切片应用_第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".
 *
 * 

* 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: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 .
 * 
* * @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(注意版本)


    org.codehaus.mojo
    aspectj-maven-plugin
    1.9
    
        1.8
        1.8
        true
        1.8
        true
    
    
        
            
                compile
                test-compile
            
        
    

方法二:Spring项目中,注入外部定义好的切片Bean.

@Configuration
@EnableAspectJAutoProxy
public class AspectConfig {
  @Bean
  public AuthorizeAspect authorizeAspect() {
    return new AuthorizeAspect();
  }
}

方法三:Spring项目中,定义切片时加上@Component

@Aspect
@Component
public class AuthorizeAspect {
}

三、总结

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

祝工作顺利, 天天开心!

你可能感兴趣的:(JAVA自定义注解并切片应用)