Java支持注解形式,合理使用注解,可以对我们的编程提供极大的便利。JAVA自身提供了三种注解,分别是:@Override,@Deprecated,@SuppreWarnings.大家平时应该看见这个这三个注解,除此之外,我们还可以自定义注解。对于自定义的注解,可以加上自己的处理逻辑,这样在某些场合,我们就可以用注解标示某些类或这个方法,这样即可做到不侵入类或者方法内部修改代码,就可以完成我们指定的功能,很是方便。
一、 JAVA自定义注解
@interface MyAnnotation{}
一个最基本的Java自定义注解形式如上所示,但是,有一些点需要注意下:
1、注解内方法不能抛出任何异常;
2、方法应该返回以下之一:基本数据类型, 字符串, 类, 枚举或这些类型的数组形式.
3、注解内方法不能有任何参数;
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数值。
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 {
}
三、总结
本文主要是总结了一下如何自定义注解,以及如何切片注解。并举了我自己的完整例子完整阐述从定义注解,到切片,以及应用主机的整个过程。
祝工作顺利, 天天开心!