Что такое аннотации?
АннотацияJDK1.5
Введен синтаксический сахар, который в основном используется в качестве метаданных, просто данные, используемые для интерпретации данных.. В Java классы, методы, переменные, параметры и пакеты могут быть аннотированы. Многие платформы с открытым исходным кодом используют аннотации, такие какSpring
,MyBatis
,Junit
. Мы обычно, вероятно, самый распространенный комментарий@Override
, эта аннотация используется для идентификации переопределенной функции.
Роль аннотаций:
-
конфигурационный файл: альтернативный
xml
файл конфигурации в текстовом формате. Использование аннотаций в качестве файлов конфигурации позволяет добиться динамической настройки кода.По сравнению с внешними файлами конфигурации аннотации позволяют сократить объем текста. Но очевиден и недостаток: изменение конфигурации требует перекомпиляции кода, которым нельзя управлять централизованно, как внешним конфигурационным файлом (поэтому сейчас это в основном смесь внешних конфигурационных файлов + аннотации). -
Маркировка данных: в качестве метки можно использовать аннотации (например:
@Override
Отмеченный метод представляет собой переопределенный метод). -
Сокращение повторяющегося кода: аннотации могут уменьшить повторяющийся и утомительный код. Например, мы определяем
@ValidateInt
, а затем получить все переменные-члены в классе с помощью отражения, если они содержат@ValidateInt
Аннотированные переменные-члены, мы можем выполнить проверку правил данных для них.
Определить аннотацию очень просто, просто следуйте следующим правилам синтаксиса:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Documented
public @interface ValidateInt {
// 它们看起来像是定义一个函数,但其实这是注解中的属性
int maxLength();
int minLength();
}
Мы обнаружили, что приведенный выше код также использует аннотации при определении аннотаций, эти аннотации называются мета-аннотациями.Аннотации, которые воздействуют на аннотации, называются метааннотациями (метааннотации на самом деле являются метаданными аннотаций).,Java
Всего есть следующие мета-аннотации.
-
@Target
: используется для описания области действия аннотации (где может использоваться аннотация).-
ElementType.CONSTRUCTOR: Конструктор.
-
ElementType.FIELD: переменная-член.
-
ElementType.LOCAL_VARIABLE: локальная переменная.
-
ElementType.PACKAGE: Пакет.
-
ElementType.parameter: параметры.
-
ElementType.method: метод.
-
ElementType.TYPE: класс, интерфейс (включая типы аннотаций) или объявление перечисления.
-
-
@Retention
: жизненный цикл аннотации, который используется для указания того, когда аннотация будет сохранена.-
RetentionPolicy.RUNTIME: сохранение во время выполнения, поэтому его можно получить с помощью отражения.
-
RetentionPolicy.CLASS: сохранение в файле класса.
-
RetentionPolicy.SOURCE: Хранение в исходных файлах.
-
-
@Documented
: Указывает, что аннотация будет использоваться в качестве общедоступного API аннотированного члена программы и, следовательно, может быть задокументирована такими инструментами, как javadoc. -
@Inherited
: Указывает, что аннотация наследуется (если для класса используется модифицированный тип аннотации @Inherited, то эта аннотация используется в этом классе как подклассе).
Поняв эти основы, затем завершите определенные выше@ValidateInt
, мы определяемCat
Затем класс использует в своих переменных-членах@ValidateInt
и выполнять проверку данных посредством отражения.
public class Cat {
private String name;
@ValidateInt(minLength = 0, maxLength = 10)
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public static void main(String[] args) throws IllegalAccessException {
Cat cat = new Cat();
cat.setName("楼楼");
cat.setAge(11);
Class<? extends Cat> clazz = cat.getClass();
Field[] fields = clazz.getDeclaredFields();
if (fields != null) {
for (Field field : fields) {
ValidateInt annotation = field.getDeclaredAnnotation(ValidateInt.class);
if (annotation != null) {
field.setAccessible(true);
int value = field.getInt(cat);
if (value < annotation.minLength()) {
// ....
} else if (value > annotation.maxLength()) {
// ....
}
}
}
}
}
}
Автор этой статьи:SylvanasSun(sylvanas.sun@gmail.com), впервые опубликовано вSylvanasSun's Blog.
Оригинальная ссылка:sylva, то есть sun.GitHub.IO/2017/10/15/…
(Для перепечатки просьба сохранить заявление в этом абзаце и сохранить гиперссылку.)
Реализация аннотаций
Аннотации на самом деле простоJava
Синтаксический сахар (синтаксический сахар — это своего рода правила синтаксиса, которые удобны для использования программистами, но на самом деле он не имеет такой волшебной функции на поверхности, но компилятор помогает программистам генерировать эти утомительные коды). существуетJava
Например, в этом синтаксическом сахаре много, например,enum
дженерики,forEach
Ждать.
чтениемJLS(Java Language Specification(Если вы хотите узнать о реализации языковой функции, лучше всего прочитать официальную спецификацию) обнаружил, чтоАннотация является наследиемjava.lang.annotation.Annotation
специальный интерфейс, Читается следующим образом:
An annotation type declaration specifies a new annotation type, a special kind of interface type. To distinguish an annotation type declaration from a normal interface declaration, the keyword interface is preceded by an at-sign (@).
Note that the at-sign (@) and the keyword interface are distinct tokens. It is possible to separate them with whitespace, but this is discouraged as a matter of style.
The rules for annotation modifiers on an annotation type declaration are specified in §9.7.4 and §9.7.5.
The Identifier in an annotation type declaration specifies the name of the annotation type.
It is a compile-time error if an annotation type has the same simple name as any of its enclosing classes or interfaces.
The direct superinterface of every annotation type is java.lang.annotation.Annotation.
package java.lang.annotation;
/**
* The common interface extended by all annotation types. Note that an
* interface that manually extends this one does <i>not</i> define
* an annotation type. Also note that this interface does not itself
* define an annotation type.
*
* More information about annotation types can be found in section 9.6 of
* <cite>The Java™ Language Specification</cite>.
*
* The {@link java.lang.reflect.AnnotatedElement} interface discusses
* compatibility concerns when evolving an annotation type from being
* non-repeatable to being repeatable.
*
* @author Josh Bloch
* @since 1.5
*/
public interface Annotation {
...
}
Мы определим вышеизложенное@ValidateInt
Аннотации декомпилированы, чтобы проверить это утверждение.
Last modified Oct 14, 2017; size 479 bytes
MD5 checksum 2d9dd2c169fe854db608c7950af3eca7
Compiled from "ValidateInt.java"
public interface com.sun.annotation.ValidateInt extends java.lang.annotation.Annotation
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
Constant pool:
#1 = Class #18 // com/sun/annotation/ValidateInt
#2 = Class #19 // java/lang/Object
#3 = Class #20 // java/lang/annotation/Annotation
#4 = Utf8 maxLength
#5 = Utf8 ()I
#6 = Utf8 minLength
#7 = Utf8 SourceFile
#8 = Utf8 ValidateInt.java
#9 = Utf8 RuntimeVisibleAnnotations
#10 = Utf8 Ljava/lang/annotation/Retention;
#11 = Utf8 value
#12 = Utf8 Ljava/lang/annotation/RetentionPolicy;
#13 = Utf8 RUNTIME
#14 = Utf8 Ljava/lang/annotation/Target;
#15 = Utf8 Ljava/lang/annotation/ElementType;
#16 = Utf8 FIELD
#17 = Utf8 Ljava/lang/annotation/Documented;
#18 = Utf8 com/sun/annotation/ValidateInt
#19 = Utf8 java/lang/Object
#20 = Utf8 java/lang/annotation/Annotation
{
public abstract int maxLength();
descriptor: ()I
flags: ACC_PUBLIC, ACC_ABSTRACT
public abstract int minLength();
descriptor: ()I
flags: ACC_PUBLIC, ACC_ABSTRACT
}
SourceFile: "ValidateInt.java"
RuntimeVisibleAnnotations:
0: #10(#11=e#12.#13)
1: #14(#11=[e#15.#16])
2: #17()
public interface com.sun.annotation.ValidateInt extends java.lang.annotation.Annotation
,Это понятноValidateInt
унаследовано отjava.lang.annotation.Annotation
.
Итак, если аннотация — это просто интерфейс, как реализовать настройку свойства? Этотак какJava
Используя динамический прокси, чтобы создать прокси-класс для интерфейса аннотации, который мы определены, и настройка атрибута аннотации фактически присваивает переменную в классе прокси. Таким образом, мы можем использовать отражение для получения различных атрибутов в аннотациях.
Чтобы подтвердить, что аннотация на самом деле является динамическим прокси-объектом, мы затем используемCLHSDB(Command-Line HotSpot Debugger)
смотретьJVM
данные времени выполнения. Если у вас есть детская обувь и вы не знаете, как ею пользоваться, вы можете обратиться к статье Р.Изучение данных времени выполнения HotSpot VM с помощью HSDB — сценарий впереди, код позади — Блог ITeye.
0x000000000257f538 com/sun/proxy/$Proxy1
Тип аннотацииcom/sun/proxy/$Proxy1
, который является типом прокси-классов по умолчанию, созданным динамическими прокси-серверами,com/sun/proxy
имя пакета по умолчанию,$Proxy
имя класса по умолчанию,1
является автоматически увеличивающимся числом.
Практика — Сканер пакетов
мы используемSpring
Когда вам нужно указать имя пакета, кадр пойдет на сканирование всех пакетов под пакетом.Spring
Аннотированный класс в . Реализовать сканер пакетов очень просто, основные идеи заключаются в следующем:
-
Сначала получите путь в проекте, передав имя входящего пакета через загрузчик классов.
-
Затем пройдите и получите все пути к файлам классов по пути (необходимо преобразовать в формат имени пакета).
-
Получив путь к файлу класса, вы можете использовать отражение для генерации объекта класса и получения в нем различной информации.
Определите интерфейс сканера пакетов:
public interface PackageScanner {
List<Class<?>> scan(String packageName);
List<Class<?>> scan(String packageName, ScannedClassHandler handler);
}
Функция 2 должна пройти вScannedClassHandler
Интерфейс — это определенная нами функция обратного вызова для обработки операций, выполняемых после сканирования всех файлов классов.
@FunctionalInterface // 这个注解表示该接口为一个函数接口,用于支持Lambda表达式
public interface ScannedClassHandler {
void execute(Class<?> clazz);
}
Я хочу, чтобы сканер пакетов распознавал и поддерживал разные типы файлов, определял класс enumResourceType
:
public enum ResourceType {
JAR("jar"),
FILE("file"),
CLASS_FILE("class"),
INVALID("invalid");
private String typeName;
public String getTypeName() {
return this.typeName;
}
private ResourceType(String typeName) {
this.typeName = typeName;
}
}
PathUtils
это служебный класс для обработки таких операций, как преобразование пути и пакета:
public class PathUtils {
private static final String FILE_SEPARATOR = System.getProperty("file.separator");
private static final String CLASS_FILE_SUFFIX = ".class";
private static final String JAR_PROTOCOL = "jar";
private static final String FILE_PROTOCOL = "file";
private PathUtils() {
}
// 去除后缀名
public static String trimSuffix(String filename) {
if (filename == null || "".equals(filename))
return filename;
int dotIndex = filename.lastIndexOf(".");
if (-1 == dotIndex)
return filename;
return filename.substring(0, dotIndex);
}
public static String pathToPackage(String path) {
if (path == null || "".equals(path))
return path;
if (path.startsWith(FILE_SEPARATOR))
path = path.substring(1);
return path.replace(FILE_SEPARATOR, ".");
}
public static String packageToPath(String packageName) {
if (packageName == null || "".equals(packageName))
return packageName;
return packageName.replace(".", FILE_SEPARATOR);
}
/**
* 根据URL的协议来判断资源类型
*/
public static ResourceType getResourceType(URL url) {
String protocol = url.getProtocol();
switch (protocol) {
case JAR_PROTOCOL:
return ResourceType.JAR;
case FILE_PROTOCOL:
return ResourceType.FILE;
default:
return ResourceType.INVALID;
}
}
public static boolean isClassFile(String path) {
if (path == null || "".equals(path))
return false;
return path.endsWith(CLASS_FILE_SUFFIX);
}
/**
* 抽取URL中的主要路径.
* Example:
* "file:/com/example/hello" to "/com/example/hello"
* "jar:file:/com/example/hello.jar!/" to "/com/example/hello.jar"
*/
public static String getUrlMainPath(URL url) throws UnsupportedEncodingException {
if (url == null)
return "";
// 如果不使用URLDecoder解码的话,路径会出现中文乱码问题
String filePath = URLDecoder.decode(url.getFile(), "utf-8");
// if file is not the jar
int pos = filePath.indexOf("!");
if (-1 == pos)
return filePath;
return filePath.substring(5, pos);
}
public static String concat(Object... args) {
if (args == null || args.length == 0)
return "";
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < args.length; i++)
stringBuilder.append(args[i]);
return stringBuilder.toString();
}
}
Когда эти вспомогательные классы определены, пришло время реализовать сканер пакетов.
public class SimplePackageScanner implements PackageScanner {
protected String packageName;
protected String packagePath;
protected ClassLoader classLoader;
private Logger logger;
public SimplePackageScanner() {
this.classLoader = Thread.currentThread().getContextClassLoader();
this.logger = LoggerFactory.getLogger(SimplePackageScanner.class);
}
@Override
public List<Class<?>> scan(String packageName) {
return this.scan(packageName, null);
}
@Override
public List<Class<?>> scan(String packageName, ScannedClassHandler handler) {
this.initPackageNameAndPath(packageName);
if (logger.isDebugEnabled())
logger.debug("Start scanning package: {} ....", this.packageName);
URL url = this.getResource(this.packagePath);
if (url == null)
return new ArrayList<>();
return this.parseUrlThenScan(url, handler);
}
private void initPackageNameAndPath(String packageName) {
this.packageName = packageName;
this.packagePath = PathUtils.packageToPath(packageName);
}
}
функцияgetResource()
Объект URL в рамках текущего проекта будет получен через загрузчик классов в соответствии с именем пакета.Если URL-адрес пустой, пустой будет возвращен напрямую.ArrayList
.
protected URL getResource(String packagePath) {
URL url = this.classLoader.getResource(packagePath);
if (url != null)
logger.debug("Get resource: {} success!", packagePath);
else
logger.debug("Get resource: {} failed,end of scan.", packagePath);
return url;
}
функцияparseUrlThenScan()
Объект URL анализируется и сканируется, в конечном итоге возвращая список классов.
protected List<Class<?>> parseUrlThenScan(URL url, ScannedClassHandler handler) {
String urlPath = "";
try {
// 先提取出URL中的路径(不含协议名等信息)
urlPath = PathUtils.getUrlMainPath(url);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
logger.debug("Get url path failed.");
}
// 判断URL的类型
ResourceType type = PathUtils.getResourceType(url);
List<Class<?>> classList = new ArrayList<>();
try {
switch (type) {
case FILE:
classList = this.getClassListFromFile(urlPath, this.packageName);
break;
case JAR:
classList = this.getClassListFromJar(urlPath);
break;
default:
logger.debug("Unsupported file type.");
}
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
logger.debug("Get class list failed.");
}
// 执行回调函数
this.invokeCallback(classList, handler);
logger.debug("End of scan <{}>.", urlPath);
return classList;
}
функцияgetClassListFromFile()
Он будет сканировать все файлы классов на пути и объединять имена пакетов для создания объектов класса.
protected List<Class<?>> getClassListFromFile(String path, String packageName) throws ClassNotFoundException {
File file = new File(path);
List<Class<?>> classList = new ArrayList<>();
File[] listFiles = file.listFiles();
if (listFiles != null) {
for (File f : listFiles) {
if (f.isDirectory()) {
// 如果是一个文件夹,则继续递归调用,注意传递的包名
List<Class<?>> list = getClassListFromFile(f.getAbsolutePath(),
PathUtils.concat(packageName, ".", f.getName()));
classList.addAll(list);
} else if (PathUtils.isClassFile(f.getName())) {
// 我们不添加名字带有$的class文件,这些都是JVM动态生成的
String className = PathUtils.trimSuffix(f.getName());
if (-1 != className.lastIndexOf("$"))
continue;
String finalClassName = PathUtils.concat(packageName, ".", className);
classList.add(Class.forName(finalClassName));
}
}
}
return classList;
}
функцияgetClassListFromJar()
Файлы классов в банке будут отсканированы.
protected List<Class<?>> getClassListFromJar(String jarPath) throws IOException, ClassNotFoundException {
if (logger.isDebugEnabled())
logger.debug("Start scanning jar: {}", jarPath);
JarInputStream jarInputStream = new JarInputStream(new FileInputStream(jarPath));
JarEntry jarEntry = jarInputStream.getNextJarEntry();
List<Class<?>> classList = new ArrayList<>();
while (jarEntry != null) {
String name = jarEntry.getName();
if (name.startsWith(this.packageName) && PathUtils.isClassFile(name))
classList.add(Class.forName(name));
jarEntry = jarInputStream.getNextJarEntry();
}
return classList;
}
функцияinvokeCallback()
Перебирает список объектов класса, а затем выполняет функцию обратного вызова.
protected void invokeCallback(List<Class<?>> classList, ScannedClassHandler handler) {
if (classList != null && handler != null) {
for (Class<?> clazz : classList) {
handler.execute(clazz);
}
}
}
Исходный адрес сканера пакетов, реализованный в этом разделе:gist.GitHub.com/Сильван как солнце…