Java 注解理解与实践

文章目录

  • Java 注解理解与实践
  • 注解Demo
  • @SuppressWarnings
  • 理解注解
  • 元注解
  • @Target:表示注解的目标
  • @Retention:注解保留到什么时候
  • @Inherited:继承
  • @Documented:形成文档
  • 注解类型
  • 标记注解
  • 元数据注解
  • 内置注解
  • Demo: @InjectResource
  • 1. anntation
  • 2. Config
  • 3. 使用

注解Demo

@SuppressWarnings

压制Java的编译警告,它有一个必填参数,表示压制哪种类型的警告,它也可以修饰大部分代码元素,在更大范围的修饰也会对内部元素起效,比如,在类上的注解会影响到方法,在方法上的注解会影响到代码行。对于Date方法的调用,可以这样压制警告:

如下代码会提示:

‘Date(int, int, int)’ 已经过时了 、Variable ‘year’ is never used

public static void main(String[] args) {
    Date date = new Date(2017, 4, 12);
    int year = date.getYear();
}

我们可以通过添加@SuppressWarnings({“deprecation”, “unused”})取消Java的编译警告!

@SuppressWarnings({"deprecation", "unused"})
public static void main(String[] args) {
    Date date = new Date(2017, 4, 12);
    int year = date.getYear();
}

查看源码

package java.lang;

import java.lang.annotation.*;
import static java.lang.annotation.ElementType.*;

/**
 * Indicates that the named compiler warnings should be suppressed in the
 * annotated element (and in all program elements contained in the annotated
 * element).  Note that the set of warnings suppressed in a given element is
 * a superset of the warnings suppressed in all containing elements.  For
 * example, if you annotate a class to suppress one warning and annotate a
 * method to suppress another, both warnings will be suppressed in the method.
 * However, note that if a warning is suppressed in a {@code
 * module-info} file, the suppression applies to elements within the
 * file and <em>not</em> to types contained within the module.
 *
 * <p>As a matter of style, programmers should always use this annotation
 * on the most deeply nested element where it is effective.  If you want to
 * suppress a warning in a particular method, you should annotate that
 * method rather than its class.
 *
 * @author Josh Bloch
 * @since 1.5
 * @jls 4.8 Raw Types
 * @jls 4.12.2 Variables of Reference Type
 * @jls 5.1.9 Unchecked Conversion
 * @jls 5.5.2 Checked Casts and Unchecked Casts
 * @jls 9.6.4.5 @SuppressWarnings
 */
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, MODULE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
    /**
     * The set of warnings that are to be suppressed by the compiler in the
     * annotated element.  Duplicate names are permitted.  The second and
     * successive occurrences of a name are ignored.  The presence of
     * unrecognized warning names is <i>not</i> an error: Compilers must
     * ignore any warning names they do not recognize.  They are, however,
     * free to emit a warning if an annotation contains an unrecognized
     * warning name.
     *
     * <p> The string {@code "unchecked"} is used to suppress
     * unchecked warnings. Compiler vendors should document the
     * additional warning names they support in conjunction with this
     * annotation type. They are encouraged to cooperate to ensure
     * that the same names work across multiple compilers.
     * @return the set of warnings to be suppressed
     */
    String[] value();
}

我们可以看到注解类的构造相对比较简单,上方有两个注解,以及一个属性。

理解注解

定义注解与定义接口有点类似,都用了interface,不过注解的interface前多了@。另外,它还有两个元注解@Target和@Retention,这两个注解专门用于定义注解本身。

元注解

@Target:表示注解的目标

ElementType是一个枚举,主要可选值有:

❑ TYPE:表示类、接口(包括注解),或者枚举声明;
 ❑ FIELD:字段,包括枚举常量;
 ❑ METHOD:方法;
 ❑ PARAMETER:方法中的参数;
 ❑ CONSTRUCTOR:构造方法;
 ❑ LOCAL_VARIABLE:本地变量;
 ❑ MODULE:模块(Java 9引入的)。

目标可以有多个,用{}表示,比如@SuppressWarnings的@Target就有多个。

如果没有声明@Target,默认为适用于所有类型。

@Retention:注解保留到什么时候

表示注解信息保留到什么时候,取值只能有一个,类型为RetentionPolicy,它是一个枚举,有三个取值。

❑ SOURCE:只在源代码中保留,编译器将代码编译为字节码文件后就会丢掉。
❑ CLASS:保留到字节码文件中,但Java虚拟机将class文件加载到内存时不一定会在内存中保留。
❑ RUNTIME:一直保留到运行时。

如果没有声明@Retention,则默认为CLASS

@Inherited:继承

与接口和类不同,注解不能继承。不过注解有一个与继承有关的元注解@Inherited,含有@Inherited注解的类,其子类自动继承了注解功能。

demo: 运行后输出tue

package cn.edu.fudan.projectmanager.component;

import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
 * @author XiaoQuanbin
 * @date 2022/3/12
 */
public class hello {
    
    @Inherited
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Test {
    }
    
    @Test
    class base{

    }
    class child extends base{

    }

    public static void main(String[] args) {
        System.out.println(child.class.isAnnotationPresent(Test.class));
    }

}

@Documented:形成文档

所有使用该Annotation修饰的程序元素的API文档中将会包含该Annotation说明。

注解类型

标记注解

注解不持有属性,仅表示标记作用

一个没有定义成员变量的Annotation类型被称为标记。这种Annotation仅利用自身的存在与否来为我们提供信息,如前面介绍的@Override、@Test等Annotation。

元数据注解

包含成员变量的Annotation,因为它们可以接受更多的元数据,所以也被称为元数据Annotation

内置注解

@Override:如果方法是在父类或接口中定义的,加上@Override吧,让编译器帮你减少错误。

@Deprecated:可以修饰的范围很广,包括类、方法、字段、参数等,它表示对应的代码已经过时了,程序员不应该使用它,不过,它是一种警告,而不是强制性的,在IDE如Eclipse中,会给Deprecated元素加一条删除线以示警告。

注解中含有属性值

package org.springframework.scheduling.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Async {
    String value() default "";
}

Demo: @InjectResource

自动注入 非bean中依赖的一些spring bean

1. anntation

package com.scan.annotation;

import java.lang.annotation.*;

/**
 * 用于 自动注入 非bean中依赖的一些spring bean
 *
 */
@Target({ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface InjectResource {

}

2. Config

package cn.edu.fudan.codetracker.scan.config;

import cn.edu.fudan.codetracker.scan.annotation.InjectResource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * description: 自动注入非单例静态资源
 *
 * @author fancying
 * create: 2022/2/13
 **/
@Slf4j
@Component
public class InjectResourceConfig {

    @Autowired
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    private ApplicationContext applicationContext;


    @PostConstruct
    public void inject() throws IllegalAccessException {
        String packageName = "cn.edu.fudan";
        List<Class<?>> classes = getClasses(packageName);

        if (classes.isEmpty()){
            log.error("no class in main package");
        }


        for (Class<?> c : classes) {
            for (Annotation a : c.getAnnotations()) {
                if (a instanceof InjectResource) {
                    for (Field field : c.getDeclaredFields()) {
                        for (Annotation fa : field.getAnnotations()) {
                            if (fa instanceof InjectResource) {
                                Class<?> clazz = field.getType();
                                Object bean = applicationContext.getBean(clazz);
                                field.setAccessible(true);
                                field.set(c, bean);
                                break;
                            }
                        }
                    }
                    break;
                }
            }
        }

    }

    /**
     * 从包package中获取所有的Class
     *
     * @param packageName packageName
     * @return classes
     */
    public static List<Class<?>> getClasses(String packageName) {

        // 第一个class类的集合
        List<Class<?>> classes = new ArrayList<>(200);
        // 获取包的名字 并进行替换
        String packageDirName = packageName.replace('.', '/');
        // 定义一个枚举的集合 并进行循环来处理这个目录下的things
        Enumeration<URL> dirs;
        try {
            dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
            // 循环迭代下去
            while (dirs.hasMoreElements()) {
                // 获取下一个元素
                URL url = dirs.nextElement();
                // 得到协议的名称
                String protocol = url.getProtocol();
                // 如果是以文件的形式保存在服务器上
                if ("file".equals(protocol)) {
                    // 获取包的物理路径
                    String filePath = URLDecoder.decode(url.getFile(), StandardCharsets.UTF_8);
                    // 以文件的方式扫描整个包下的文件 并添加到集合中
                    findAndAddClassesInPackageByFile(packageName, filePath, true, classes);
                } else if ("jar".equals(protocol)) {
                    // 如果是jar包文件
                    // 定义一个JarFile
                    JarFile jar;
                    try {
                        // 获取jar
                        jar = ((JarURLConnection) url.openConnection()).getJarFile();
                        // 从此jar包 得到一个枚举类
                        Enumeration<JarEntry> entries = jar.entries();
                        // 同样的进行循环迭代
                        while (entries.hasMoreElements()) {
                            // 获取jar里的一个实体 可以是目录 和一些jar包里的其他文件 如META-INF等文件
                            JarEntry entry = entries.nextElement();
                            String name = entry.getName();
                            // 如果是以/开头的
                            if (name.charAt(0) == '/') {
                                // 获取后面的字符串
                                name = name.substring(1);
                            }
                            // 如果前半部分和定义的包名相同
                            if (name.startsWith(packageDirName)) {
                                int idx = name.lastIndexOf('/');
                                // 如果以"/"结尾 是一个包
                                if (idx != -1) {
                                    // 获取包名 把"/"替换成"."
                                    packageName = name.substring(0, idx).replace('/', '.');
                                }
                                // 如果可以迭代下去 并且是一个包
                                // 如果是一个.class文件 而且不是目录
                                if (name.endsWith(".class") && !entry.isDirectory()) {
                                    // 去掉后面的".class" 获取真正的类名
                                    String className = name.substring(packageName.length() + 1, name.length() - 6);
                                    try {
                                        // 添加到classes
                                        classes.add(Class.forName(packageName + '.' + className));
                                    } catch (ClassNotFoundException e) {
                                        e.printStackTrace();
                                    }
                                }
                            }
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        return classes;
    }


    /**
     * 以文件的形式来获取包下的所有Class
     *
     * @param packageName name
     * @param packagePath path
     * @param recursive default true
     * @param classes classes
     */
    public static void findAndAddClassesInPackageByFile(String packageName, String packagePath, final boolean recursive,
                                                        List<Class<?>> classes) {
        // 获取此包的目录 建立一个File
        File dir = new File(packagePath);
        // 如果不存在或者 也不是目录就直接返回
        if (!dir.exists() || !dir.isDirectory()) {
            return;
        }
        // 如果存在 就获取包下的所有文件 包括目录
        // 自定义过滤规则 如果可以循环(包含子目录) 或则是以.class结尾的文件(编译好的java类文件)
        File[] dirFiles = dir.listFiles(file -> (recursive && file.isDirectory()) || (file.getName().endsWith(".class")));

        if (dirFiles == null) {
            return;
        }

        // 循环所有文件
        for (File file : dirFiles) {
            // 如果是目录 则继续扫描
            if (file.isDirectory()) {
                findAndAddClassesInPackageByFile(packageName + "." + file.getName(), file.getAbsolutePath(), recursive,
                        classes);
            } else {
                // 如果是java类文件 去掉后面的.class 只留下类名
                String className = file.getName().substring(0, file.getName().length() - 6);
                try {
                    // 添加到集合中去
                    classes.add(Class.forName(packageName + '.' + className));
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }
            }
        }
    }


}

3. 使用

package com.scan.service.impl;

import com.scan.annotation.InjectResource;


@InjectResource
public class ToolScanImpl{

    @InjectResource
    private static AccountDao accountDao;
    @InjectResource
    private static ProxyDao proxyDao;
    @InjectResource
    private static ScanMapper scanMapper;
    @InjectResource
    private static CodeTrackerRestInterfaceManager rest;

}