Java 注解理解与实践
文章目录
注解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;
}