注解浅析

注解是JDK5的新特性,是一种特殊的类,它作用相当于一个注释和标记,它可以应用于包,类(类,接口,枚举和注释类型),变量(类,实例和局部变量 - 包括在for或while循环中定义的变量),构造函数,方法和参数。注解的使用减少了大量配置文件的使用,使代码更加易读。

注解是一个特殊的接口,使用@interface定义注释类型,注解中元素值允许基础类型,字符串,枚举,Class和它们的数组。元素使用与方法非常相似的语法定义,但是不允许使用修饰符和参数,如下示例:

public @interface RequestLimit {
    String name();
    String created();
    int revision() default 1;
    String[] reviewers() default {};
    LimitType limitType() default LimitType.DAY_LIMIT;
}
public enum  LimitType {
    SECUND_LIMIT,
    MI_LIMIT,
    DAY_LIMIT,
    MON_LIMIT,
    YEAR_LIMIT;
}

 Java中定义了三种内置注释类型 , 它们由Java编译器使用:

  • @Deprecated:表示不应使用标记的元素。只要使用标记的元素,编译器就会生成警告。它应该与Javadoc @deprecated一起使用,保留Javadoc来解释弃用元素的动机。
  • @Override:表示该元素用于覆盖超类中声明的元素。如果编译器找到一个标记的元素并且它实际上没有覆盖任何内容,它将生成警告。虽然不需要它,但检测错误很有用 - 例如,如果在创建子类之后,其他人修改了超类方法签名,我们将在重建源代码时立即收到警告。
  • @SuppressWarnings:向编译器指示它应该抑制标记元素会产生的某些特定警告。

 @Deprecated注解使用: 

 

被 @Deprecated 注解注释的方法被使用,编译器会在使用时在方法名称上加横线提示过期。

 @Override注解使用: 

被 @Override 注解注释的方法,它实际上没有覆盖其他方法,编译器会标红警告。

@SuppressWarnings注解使用: 

 如图,上面@Deprecated 测试中,介绍被Deprecated标记的方法,方法名称会画横线,这次加入@SuppressWarnings("deprecation")注解后,方法名称不在画横线提示。 

 

 

 

若想自定义java注解,我们首先需要了解java元注解。

Java中的元注解是专职负责注解其他注解的,用来标示其他注解的适用范围和作用域。

Java中有四个元注解。

元注解描述
@Target

用于描述注解的使用范围。可能的ElementType参数包括:

CONSTRUCTOR:构造器的生命

FIELD:域声明(包括enum实例)

LOCAL_VARIABLE:局部变量声明

METHOD:方法声明

PACKAGE:包声明

PARAMETER:参数声明

TYPE:类、接口(包括注解类型)和enum声明

ANNOTATION_TYPE:注解声明(与TYPE的区别?,专门用在注解上的TYPE)

TYPE_PARAMETER:Java8新增类型参数声明

TYPE_USE:Java8新增类型的使用

@Retention

表示需要在什么级别保存该注解信息。可选的RetentionPolicy参数包括:

SOURCE:即此注解只能保存在源代码中 ,注解将在编译器丢弃

CLASS:默认,注解在class文件中可用,但会被VM丢弃,运行时无法访问

RUNTIME:VM将在运行期也保留注解,因此可以通过反射机制读取注解的信息

@Documented表示每次在带注释的元素中找到标记的注释类型时,应由Javadoc记录。
@Inherited允许子类继承父类中的注解。即拥有此注解的元素其子类可以继承父类的注解。仅适用于类继承而不适用于接口实现。

了解了元注解之后,我们就可以动手写一些自己的自定义注解了。

如下代码:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Inherited
public @interface RequestLimit {
    int time() default 1;

    int count() default 5;
}

 

 

 


 

 

一般来说Annotation有如下三种使用情形:

  • 为编译器提供辅助信息 — Annotations可以为编译器提供额外信息,以便于检测错误,抑制警告等,使用RetentionPolicy.SOURCE
  • 编译源代码时进行额外操作 — 软件工具可以通过处理Annotation信息来生成原代码,xml文件等等使用RetentionPolicy.CLASS
  • 运行时处理 — 有一些annotation可以在程序运行时被检测,使用RetentionPolicy.RUNTIME

自定义注解既然定义完成了,那么自定义注解要如何具体的工作。通常我们按照@Retention注解的参数,使用不同方法。

RetentionPolicy.RUNTIME 运行时注解处理,使用反射处理:

注解类

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ThansEnumValue {
    String value() default "";

    Class enumClazz() default void.class;
}

 注解使用实体类

public class LimitInfoDTO {
    @ThansEnumValue(enumClazz = LimitType.class)
    private String limitType;//额度类型
    private String limitAmt;//额度金额
    private String limitBegin;//额度起始日
    private String limitEnd;//额度结束日
    @ThansEnumValue(enumClazz = LimitStatus.class)
    private String limitStatus;//额度状态

}

注解生效业务方法

 public static void thansEnum(Object object,JSONObject param){
        Class clazz=object.getClass();
        Field[] fields = clazz.getDeclaredFields();
        for(Field field : fields ){
            String propertyName ="";
            String value="";
            try {
                propertyName = field.getName();
                if (!field.isAnnotationPresent(ThansEnumValue.class)){
                    continue;
                }
                Method getMethod=clazz.getMethod(getMethodName(propertyName, field.getType().getTypeName()),new Class[] {});
                Method setMethod=clazz.getMethod(setMethodName(propertyName),new Class[] {field.getType()});
                value=String.valueOf(getMethod.invoke(object,new Object[]{}));
                //跳过空 和"null"数据的
                if (StringUtils.isEmpty(value) || GlobalConfig.NULL.equals(value.toLowerCase())){
                    continue;
                }
                String newValue=null;
                ThansEnumValue thansEnumValue=field.getAnnotation(ThansEnumValue.class);
                Class enumClazz=thansEnumValue.enumClazz();
                newValue= EnumFactory.getNameByKey(enumClazz,value);
                setMethod.invoke(object,new Object[]{newValue});
            }catch (Exception e){
                logger.warn(" thans fail propertyName: {} ,value :{},{}",propertyName,value,e);
            }

        }
    }

如上代码中通过实体反射,获得方法注解等等,继而获取注解的属性值,根据注解的属性值,工厂类生成业务中需要的实体类,具体的影响到业务逻辑。

在spring项目开发中,我们通常使用aop编程方法,使用自定义注解:

@Component
@Aspect
public class RequestLimitAdivice {
    private static Logger logger = LoggerFactory.getLogger(RequestLimitAdivice.class);

    @Autowired
    RedissonClient redissonClient;


    @Before("within(cn.keking.project.mobileoffice.controller..*)  && @annotation(requestLimit)")
    public void requestLimit(JoinPoint joinPoint, RequestLimit requestLimit) throws Throwable {
        Object args[] = joinPoint.getArgs();
        HttpServletRequest request = null;
        request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        if (request == null) {
            return;
        }
        String ipAddress = HttpClientUtil.getIpAddr(request);
        String uri = request.getRequestURI();
        String key = uri + "_" + ipAddress;
        RAtomicLong result = redissonClient.getAtomicLong(key);
        long count = result.incrementAndGet();
        logger.info("key :{} ,count :{}", key, count);
        //第一次访问
        if (count <= 1) {
            result.expire(requestLimit.time(), TimeUnit.SECONDS);
        }
        if (count > requestLimit.count()) {
            logger.info("地址" + key + "访问频率过快。");
            throw new BusinessException(BusinessResultEnum.REQUEST_LIMIT);
        }
    }
}

RetentionPolicy.CLASS使用自定义注解处理器:

注释处理器只是一个实现javax.annotation.processing.Processor接口并遵守给定契约的类。为方便起见,javax.annotation.processing.AbstractProcessor类中提供了一个具有自定义处理器通用功能的抽象实现。

自定义处理器可以使用三个注释来配置自身:

  • javax.annotation.processing.SupportedAnnotationTypes:此批注用于注册处理器支持的批注。有效值是注释类型的完全限定名称 - 允许使用通配符。
  • javax.annotation.processing.SupportedSourceVersion:此批注用于注册处理器支持的源版本。
  • javax.annotation.processing.SupportedOptions:此批注用于注册可通过命令行传递的允许的自定义选项。

要与带注释的类进行交互,process()方法接收两个参数:

  • 一组java.lang.model.TypeElement对象:注释处理在一轮或多轮中完成。在每一轮中,处理器被调用,并且它们在该集合中接收在当前轮次中处理的注释的类型。
  • javax.annotation.processing.RoundEnvironment对象:此对象允许访问在当前轮次和上一轮中处理的带注释的源元素。

除了这两个参数之外,processingEnv实例变量中还有一个ProcessingEnvironment对象。此对象可以访问日志以及一些实用程序。

使用RoundEnvironment对象和Element接口的反射方法,我们可以为注释处理器编写一个简单的实现。

 

 

public class RunProcessorDemo extends AbstractProcessor {

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        for (Element element : roundEnv.getElementsAnnotatedWith(DemoProcessorClass.class)) {
            System.out.println("------------------------------");
            if (element.getKind() == ElementKind.CLASS) {
                TypeElement typeElement = (TypeElement) element;
                // 输出元素名称
                System.out.println(typeElement.getSimpleName());
                System.out.println(typeElement.getAnnotation(DemoProcessorClass.class).discribe());
            }
        }

        for (Element element : roundEnv.getElementsAnnotatedWith(DemoProcessorSource.class)) {
            System.out.println("------------------------------");
            if (element.getKind() == ElementKind.METHOD) {
                TypeElement typeElement = (TypeElement) element;
                // 输出元素名称
                System.out.println(typeElement.getSimpleName());
                System.out.println(typeElement.getAnnotation(DemoProcessorSource.class).discribe());
            }
        }

        for (Element element : roundEnv.getElementsAnnotatedWith(DemoProcessorRuning.class)) {
            System.out.println("------------------------------");
            if (element.getKind() == ElementKind.METHOD) {
                TypeElement typeElement = (TypeElement) element;
                // 输出元素名称
                System.out.println(typeElement.getSimpleName());
                System.out.println(typeElement.getAnnotation(DemoProcessorRuning.class).discribe());
            }
        }
        return false;
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> set=new HashSet<>();
        set.add(DemoProcessorClass.class.getCanonicalName());
        set.add(DemoProcessorRuning.class.getCanonicalName());
        set.add(DemoProcessorSource.class.getCanonicalName());
        return set;
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return  SourceVersion.latestSupported();
    }
}

 

 

注解处理器是(Annotation Processor)是javac的一个工具,用来在编译时扫描、编译和处理注解(Annotation)。注解处理器运行在它自己的 JVM 中。javac 启动了一个完整的 java 虚拟机来运行注解处理器,注册我的注解处理器到 javac,必须提供一个.jar文件,。并且,你的 .jar 文件中,你必须打包一个特殊的文件javax.annotation.processing.ProcessorMETA-INF/services目录下。javax.annotation.processing.Processor 文件的内容是一个列表,每一行是一个注解处理器的全称。例如:

文件内容:

# Copyright MapStruct Authors.
#
# Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0

cn.keking.project.mobileoffice.infrastructure.annotation.RequestLimit

也可以引用谷歌开发的开源工具,注解

@AutoService(Processor.class)自动生成jar。引用如下
 <!-- https://mvnrepository.com/artifact/com.google.auto.service/auto-service -->
        <dependency>
            <groupId>com.google.auto.service</groupId>
            <artifactId>auto-service</artifactId>
            <version>1.0-rc2</version>
        </dependency>

添加注解 实现接口AbstractProcessor 

@SupportedAnnotationTypes("cn.keking.project.mobileoffice.infrastructure.annotation.RequestLimit")
@SupportedSourceVersion(value= SourceVersion.RELEASE_8)
@AutoService(Processor.class)
public class MyAnnoProcess extends AbstractProcessor {
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        //  通过RoundEnvironment获取到所有被@RequestLimit注解的对象
         for (Element element : roundEnv.getElementsAnnotatedWith(RequestLimit.class)) {
            System.out.println("------------------------------");
            if (element.getKind() == ElementKind.CLASS) {
                TypeElement typeElement = (TypeElement) element;
                // 输出元素名称
                System.out.println(typeElement.getSimpleName());
                System.out.println(typeElement.getAnnotation(RequestLimit.class).discribe());
            }
        }
        return true;
    }
}

简单的注解调试:

如图,勾选上运行注解解析器,编译时即可debug调试

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值