Java最新Spring系列第16篇:深入理解java注解(预备知识),应届生面试java开发工程师的题

知其然不知其所以然,大厂常问面试技术如何复习?

1、热门面试题及答案大全

面试前做足功夫,让你面试成功率提升一截,这里一份热门350道一线互联网常问面试题及答案助你拿offer

2、多线程、高并发、缓存入门到实战项目pdf书籍

3、文中提到面试题答案整理

4、Java核心知识面试宝典

覆盖了JVM 、JAVA集合、JAVA多线程并发、JAVA基础、Spring原理、微服务、Netty与RPC、网络、日志、Zookeeper、Kafka、RabbitMQ、Hbase、MongoDB 、Cassandra、设计模式、负载均衡、数据库、一致性算法 、JAVA算法、数据结构、算法、分布式缓存、Hadoop、Spark、Storm的大量技术点且讲解的非常深入

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

一个参数为value的注解,可以省略参数名称

只有一个参数,名称为value的时候,使用时参数名称可以省略

@Target(ElementType.TYPE)

@Retention(RetentionPolicy.RUNTIME)

@interface Ann3 {

String value();//@1

}

@Ann3(“我是路人甲java”) //@2

public class UseAnnotation3 {

}

@1:注解之后一个参数,名称为value

@2:使用注解,参数名称value省略了

数组类型参数

@Target(ElementType.TYPE)

@Retention(RetentionPolicy.RUNTIME)

@interface Ann4 {

String[] name();//@1

}

@Ann4(name = {“我是路人甲java”, “欢迎和我一起学spring”}) //@2

public class UseAnnotation4 {

@Ann4(name = “如果只有一个值,{}可以省略”) //@3

public class T1 {

}

}

@1:name的类型是一个String类型的数组

@2:name有多个值的时候,需要使用{}包含起来

@3:如果name只有一个值,{}可以省略

为参数指定默认值

通过default为参数指定默认值,用的时候如果没有设置值,则取默认值,没有指定默认值的参数,使用的时候必须为参数设置值,如下:

@Target(ElementType.TYPE)

@Retention(RetentionPolicy.RUNTIME)

@interface Ann5 {

String[] name() default {“路人甲java”, “spring系列”};//@1

int[] score() default 1; //@2

int age() default 30; //@3

String address(); //@4

}

@Ann5(age = 32,address = “上海”) //@5

public class UseAnnotation5 {

}

@1:数组类型通过{}指定默认值

@2:数组类型参数,默认值只有一个省略了{}符号

@3:默认值为30

@4:未指定默认值

@5:age=32对默认值进行了覆盖,并且为address指定了值

综合案例

@Target(value = {

ElementType.TYPE,

ElementType.METHOD,

ElementType.FIELD,

ElementType.PARAMETER,

ElementType.CONSTRUCTOR,

ElementType.LOCAL_VARIABLE

})

@Retention(RetentionPolicy.RUNTIME)

@interface Ann6 {

String value();

ElementType elementType();

}

@Ann6(value = “我用在类上”, elementType = ElementType.TYPE)

public class UseAnnotation6 {

@Ann6(value = “我用在字段上”, elementType = ElementType.FIELD)

private String a;

@Ann6(value = “我用在构造方法上”, elementType = ElementType.CONSTRUCTOR)

public UseAnnotation6(@Ann6(value = “我用在方法参数上”, elementType = ElementType.PARAMETER) String a) {

this.a = a;

}

@Ann6(value = “我用在了普通方法上面”, elementType = ElementType.METHOD)

public void m1() {

@Ann6(value = “我用在了本地变量上”, elementType = ElementType.LOCAL_VARIABLE) String a;

}

}

上面演示了自定义注解在在类、字段、构造器、方法参数、方法、本地变量上的使用,@Ann6注解有个elementType参数,我想通过这个参数的值来告诉大家对应@Target中的那个值来限制使用目标的,大家注意一下上面每个elementType的值。

@Target(ElementType.TYPE_PARAMETER)

这个是1.8加上的,用来标注类型参数,类型参数一般在类后面声明或者方法上声明,这块需要先了解一下泛型泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!不然理解起来比较吃力,来个案例感受一下:

@Target(value = {

ElementType.TYPE_PARAMETER

})

@Retention(RetentionPolicy.RUNTIME)

@interface Ann7 {

String value();

}

public class UseAnnotation7<@Ann7(“T0是在类上声明的一个泛型类型变量”) T0, @Ann7(“T1是在类上声明的一个泛型类型变量”) T1> {

public <@Ann7(“T2是在方法上声明的泛型类型变量”) T2> void m1() {

}

public static void main(String[] args) throws NoSuchMethodException {

for (TypeVariable typeVariable : UseAnnotation7.class.getTypeParameters()) {

print(typeVariable);

}

for (TypeVariable typeVariable : UseAnnotation7.class.getDeclaredMethod(“m1”).getTypeParameters()) {

print(typeVariable);

}

}

private static void print(TypeVariable typeVariable) {

System.out.println(“类型变量名称:” + typeVariable.getName());

Arrays.stream(typeVariable.getAnnotations()).forEach(System.out::println);

}

}

类和方法上面可以声明泛型类型的变量,上面有3个泛型类型变量,我们运行一下看看效果:

类型变量名称:T0

@com.javacode2018.lesson001.demo18.Ann7(value=T0是在类上声明的一个泛型类型变量)

类型变量名称:T1

@com.javacode2018.lesson001.demo18.Ann7(value=T1是在类上声明的一个泛型类型变量)

类型变量名称:T2

@com.javacode2018.lesson001.demo18.Ann7(value=T2是在方法上声明的泛型类型变量)

@Target(ElementType.TYPE_USE)

这个是1.8加上的,能用在任何类型名称上,来个案例感受一下:

@Target({ElementType.TYPE_USE})

@Retention(RetentionPolicy.RUNTIME)

@interface Ann10 {

String value();

}

@Ann10(“用在了类上”)

public class UserAnnotation10<@Ann10(“用在了类变量类型V1上”) V1, @Ann10(“用在了类变量类型V2上”) V2> {

private Map<@Ann10(“用在了泛型类型上”) String, Integer> map;

public <@Ann10(“用在了参数上”) T> String m1(String name) {

return null;

}

}

类后面的V1、V2都是类型名称,Map后面的尖括号也是类型名称,m1方法前面也定义了一个类型变量,名称为T

注解信息的获取


为了运行时能准确获取到注解的相关信息,Java在java.lang.reflect 反射包下新增了AnnotatedElement接口,它主要用于表示目前正在虚拟机中运行的程序中已使用注解的元素,通过该接口提供的方法可以利用反射技术地读取注解的信息,看一下UML图:

  • Package:用来表示包的信息

  • Class:用来表示类的信息

  • Constructor:用来表示构造方法信息

  • Field:用来表示类中属性信息

  • Method:用来表示方法信息

  • Parameter:用来表示方法参数信息

  • TypeVariable:用来表示类型变量信息,如:类上定义的泛型类型变量,方法上面定义的泛型类型变量

AnnotatedElement常用方法

案例

要解析的列如下

package com.javacode2018.lesson001.demo18;

import java.lang.annotation.ElementType;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;

import java.util.Map;

@Target({ElementType.PACKAGE,

ElementType.TYPE,

ElementType.FIELD,

ElementType.CONSTRUCTOR,

ElementType.METHOD,

ElementType.PARAMETER,

ElementType.TYPE_PARAMETER,

ElementType.TYPE_USE})

@Retention(RetentionPolicy.RUNTIME)

@interface Ann11 {

String value();

}

@Target({ElementType.PACKAGE,

ElementType.TYPE,

ElementType.FIELD,

ElementType.CONSTRUCTOR,

ElementType.METHOD,

ElementType.PARAMETER,

ElementType.TYPE_PARAMETER,

ElementType.TYPE_USE})

@Retention(RetentionPolicy.RUNTIME)

@interface Ann11_0 {

int value();

}

@Ann11(“用在了类上”)

@Ann11_0(0)

public class UseAnnotation11<@Ann11(“用在了类变量类型V1上”) @Ann11_0(1) V1, @Ann11(“用在了类变量类型V2上”) @Ann11_0(2) V2> {

@Ann11(“用在了字段上”)

@Ann11_0(3)

private String name;

private Map<@Ann11(“用在了泛型类型上,String”) @Ann11_0(4) String, @Ann11(“用在了泛型类型上,Integer”) @Ann11_0(5) Integer> map;

@Ann11(“用在了构造方法上”)

@Ann11_0(6)

public UseAnnotation11() {

this.name = name;

}

@Ann11(“用在了返回值上”)

@Ann11_0(7)

public String m1(@Ann11(“用在了参数上”) @Ann11_0(8) String name) {

return null;

}

}

解析类上的注解

解析这部分

@Ann11(“用在了类上”)

代码

@Test

public void m1() {

for (Annotation annotation : UserAnnotation10.class.getAnnotations()) {

System.out.println(annotation);

}

}

运行输出

@com.javacode2018.lesson001.demo18.Ann11(value=用在了类上)

@com.javacode2018.lesson001.demo18.Ann11_0(value=0)

解析类上的类型变量

解析类名后面的尖括号的部分,即下面的部分:

UseAnnotation11<@Ann11(“用在了类变量类型V1上”) @Ann11_0(1) V1, @Ann11(“用在了类变量类型V2上”) @Ann11_0(2) V2>

用例代码

@Test

public void m2() {

TypeVariable<Class>[] typeParameters = UserAnnotation10.class.getTypeParameters();

for (TypeVariable<Class> typeParameter : typeParameters) {

System.out.println(typeParameter.getName() + “变量类型注解信息:”);

Annotation[] annotations = typeParameter.getAnnotations();

for (Annotation annotation : annotations) {

System.out.println(annotation);

}

}

}

运行输出

V1变量类型注解信息:

@com.javacode2018.lesson001.demo18.Ann11(value=用在了类变量类型V1上)

@com.javacode2018.lesson001.demo18.Ann11_0(value=1)

V2变量类型注解信息:

@com.javacode2018.lesson001.demo18.Ann11(value=用在了类变量类型V2上)

@com.javacode2018.lesson001.demo18.Ann11_0(value=2)

解析字段name上的注解

用例代码

@Test

public void m3() throws NoSuchFieldException {

Field nameField = UserAnnotation10.class.getDeclaredField(“name”);

for (Annotation annotation : nameField.getAnnotations()) {

System.out.println(annotation);

}

}

运行输出

@com.javacode2018.lesson001.demo18.Ann11(value=用在了字段上)

@com.javacode2018.lesson001.demo18.Ann11_0(value=3)

解析泛型字段map上的注解

用例代码

@Test

public void m4() throws NoSuchFieldException, ClassNotFoundException {

Field field = UseAnnotation11.class.getDeclaredField(“map”);

Type genericType = field.getGenericType();

Type[] actualTypeArguments = ((ParameterizedType) genericType).getActualTypeArguments();

AnnotatedType annotatedType = field.getAnnotatedType();

AnnotatedType[] annotatedActualTypeArguments = ((AnnotatedParameterizedType) annotatedType).getAnnotatedActualTypeArguments();

int i = 0;

for (AnnotatedType actualTypeArgument : annotatedActualTypeArguments) {

Type actualTypeArgument1 = actualTypeArguments[i++];

System.out.println(actualTypeArgument1.getTypeName() + “类型上的注解如下:”);

for (Annotation annotation : actualTypeArgument.getAnnotations()) {

System.out.println(annotation);

}

}

}

运行输出

java.lang.String类型上的注解如下:

@com.javacode2018.lesson001.demo18.Ann11(value=用在了泛型类型上,String)

@com.javacode2018.lesson001.demo18.Ann11_0(value=4)

java.lang.Integer类型上的注解如下:

@com.javacode2018.lesson001.demo18.Ann11(value=用在了泛型类型上,Integer)

@com.javacode2018.lesson001.demo18.Ann11_0(value=5)

解析构造函数上的注解

用例代码

@Test

public void m5() {

Constructor<?> constructor = UseAnnotation11.class.getConstructors()[0];

for (Annotation annotation : constructor.getAnnotations()) {

System.out.println(annotation);

}

}

运行输出

@com.javacode2018.lesson001.demo18.Ann11(value=用在了构造方法上)

@com.javacode2018.lesson001.demo18.Ann11_0(value=6)

解析m1方法上的注解

用例代码

@Test

public void m6() throws NoSuchMethodException {

Method method = UseAnnotation11.class.getMethod(“m1”, String.class);

for (Annotation annotation : method.getAnnotations()) {

System.out.println(annotation);

}

}

运行输出

@com.javacode2018.lesson001.demo18.Ann11(value=用在了返回值上)

@com.javacode2018.lesson001.demo18.Ann11_0(value=7)

解析m1方法参数注解

用例代码

@Test

public void m7() throws NoSuchMethodException {

Method method = UseAnnotation11.class.getMethod(“m1”, String.class);

for (Parameter parameter : method.getParameters()) {

System.out.println(String.format(“参数%s上的注解如下:”, parameter.getName()));

for (Annotation annotation : parameter.getAnnotations()) {

System.out.println(annotation);

}

}

}

运行输出

参数arg0上的注解如下:

@com.javacode2018.lesson001.demo18.Ann11(value=用在了参数上)

@com.javacode2018.lesson001.demo18.Ann11_0(value=8)

上面参数名称为arg0,如果想让参数名称和源码中真实名称一致,操作如下:

如果你编译这个class的时候没有添加参数–parameters,运行的时候你会得到这个结果:

Parameter: arg0

编译的时候添加了–parameters参数的话,运行结果会不一样:

Parameter: args

对于有经验的Maven使用者,–parameters参数可以添加到maven-compiler-plugin的配置部分:

org.apache.maven.plugins

maven-compiler-plugin

3.1

-parameters

1.8

1.8

@Inherit:实现类之间的注解继承


用法

来看一下这个注解的源码

@Documented

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.ANNOTATION_TYPE)

public @interface Inherited {

}

我们通过@Target元注解的属性值可以看出,这个@Inherited 是专门修饰注解的。

作用:让子类可以继承父类中被@Inherited修饰的注解,注意是继承父类中的,如果接口中的注解也使用@Inherited修饰了,那么接口的实现类是无法继承这个注解的

案例

package com.javacode2018.lesson001.demo18;

import java.lang.annotation.*;

public class InheritAnnotationTest {

@Target(ElementType.TYPE)

@Retention(RetentionPolicy.RUNTIME)

@Inherited

@interface A1{ //@1

}

@Target(ElementType.TYPE)

@Retention(RetentionPolicy.RUNTIME)

@Inherited

@interface A2{ //@2

}

@A1 //@3

interface I1{}

@A2 //@4

static class C1{}

static class C2 extends C1 implements I1{} //@5

public static void main(String[] args) {

for (Annotation annotation : C2.class.getAnnotations()) { //@6

System.out.println(annotation);

}

}

}

@1:定义了一个注解A1,上面使用了@Inherited,表示这个具有继承功能

@2:定义了一个注解A2,上面使用了@Inherited,表示这个具有继承功能

@3:定义接口I1,上面使用了@A1注解

@4:定义了一个C1类,使用了A2注解

@5:C2继承了C1并且实现了I1接口

@6:获取C2上以及从父类继承过来的所有注解,然后输出

运行输出:

@com.javacode2018.lesson001.demo18.InheritAnnotationTest$A2()

从输出中可以看出类可以继承父类上被@Inherited修饰的注解,而不能继承接口上被@Inherited修饰的注解,这个一定要注意

@Repeatable重复使用注解


来看一段代码:

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.TYPE)

@interface Ann12{}

@Ann12

@Ann12

public class UseAnnotation12 {

}

上面代码会报错,原因是:UseAnnotation12上面重复使用了@Ann12注解,默认情况下@Ann12注解是不允许重复使用的。

像上面这样,如果我们想重复使用注解的时候,需要用到@Repeatable注解

使用步骤

先定义容器注解

@Retention(RetentionPolicy.RUNTIME)

@Target({ElementType.TYPE, ElementType.FIELD})

@interface Ann12s {

Ann12[] value(); //@1

}

容器注解中必须有个value类型的参数,参数类型为子注解类型的数组。

为注解指定容器

要让一个注解可以重复使用,需要在注解上加上@Repeatable注解,@Repeatable中value的值为容器注解,如下代码中的@2

@Retention(RetentionPolicy.RUNTIME)

@Target({ElementType.TYPE, ElementType.FIELD})

@Repeatable(Ann12s.class)//@2

@interface Ann12 {

String name();

}

使用注解

重复使用相同的注解有2种方式,如下面代码

  1. 重复使用注解,如下面的类上重复使用@Ann12注解

  2. 通过容器注解来使用更多个注解,如下面的字段v1上使用@Ann12s容器注解

@Ann12(name = “路人甲Java”)

@Ann12(name = “Spring系列”)

public class UseAnnotation12 {

@Ann12s(

{@Ann12(name = “Java高并发系列,见公众号”),

@Ann12(name = “mysql高手系列,见公众号”)}

)

private String v1;

}

获取注解信息

com.javacode2018.lesson001.demo18.UseAnnotation12

@Test

public void test1() throws NoSuchFieldException {

Annotation[] annotations = UseAnnotation12.class.getAnnotations();

for (Annotation annotation : annotations) {

System.out.println(annotation);

}

System.out.println(“-------------”);

Field v1 = UseAnnotation12.class.getDeclaredField(“v1”);

Annotation[] declaredAnnotations = v1.getDeclaredAnnotations();

for (Annotation declaredAnnotation : declaredAnnotations) {

System.out.println(declaredAnnotation);

}

}

运行输出:

@com.javacode2018.lesson001.demo18.Ann12s(value=[@com.javacode2018.lesson001.demo18.Ann12(name=路人甲Java), @com.javacode2018.lesson001.demo18.Ann12(name=Spring系列)])


@com.javacode2018.lesson001.demo18.Ann12s(value=[@com.javacode2018.lesson001.demo18.Ann12(name=Java高并发系列,见公众号), @com.javacode2018.lesson001.demo18.Ann12(name=mysql高手系列,见公众号)])

上面就是java中注解的功能,下面我们来介绍spring对于注解方面的支持。

先来看一个问题


代码如下:

package com.javacode2018.lesson001.demo18;

import org.junit.Test;

import org.springframework.core.annotation.AnnotatedElementUtils;

import org.springframework.core.annotation.AnnotationUtils;

import java.lang.annotation.ElementType;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;

@Target(ElementType.TYPE)

@Retention(RetentionPolicy.RUNTIME)

@interface A1 {

String value() default “a”;//@0

}

@Target(ElementType.TYPE)

@Retention(RetentionPolicy.RUNTIME)

@A1

@interface B1 { //@1

String value() default “b”;//@2

}

@B1(“路人甲Java”) //@3

public class UseAnnotation13 {

@Test

public void test1() {

//AnnotatedElementUtils是spring提供的一个查找注解的工具类

System.out.println(AnnotatedElementUtils.getMergedAnnotation(UseAnnotation13.class, B1.class));

System.out.println(AnnotatedElementUtils.getMergedAnnotation(UseAnnotation13.class, A1.class));

}

}

@0:A1注解value参数值默认为a

@1:B1注解上使用到了@A1注解

@2:B1注解value参数值默认为b

@2:UseAnnotation13上面使用了@B1注解,value参数的值为:路人甲java

test1方法中使用到了spring中的一个类AnnotatedElementUtils,通过这个工具类可以很方便的获取注解的各种信息,方法中的2行代码用于获取UseAnnotation13类上B1注解和A1注解的信息。

运行test1方法输出:

@com.javacode2018.lesson001.demo18.B1(value=路人甲Java)

@com.javacode2018.lesson001.demo18.A1(value=a)

上面用法很简单,没什么问题。

此时有个问题:此时如果想在UseAnnotation13上给B1上的A1注解设置值是没有办法的,注解定义无法继承导致的,如果注解定义上面能够继承,那用起来会爽很多,spring通过@Aliasfor方法解决了这个问题。

Spring  @AliasFor:对注解进行增强


直接上案例,然后解释代码。

案例1:通过@AliasFor解决刚才难题

感受:

其实我投简历的时候,都不太敢投递阿里。因为在阿里一面前已经过了字节的三次面试,投阿里的简历一直没被捞,所以以为简历就挂了。

特别感谢一面的面试官捞了我,给了我机会,同时也认可我的努力和态度。对比我的面经和其他大佬的面经,自己真的是运气好。别人8成实力,我可能8成运气。所以对我而言,我要继续加倍努力,弥补自己技术上的不足,以及与科班大佬们基础上的差距。希望自己能继续保持学习的热情,继续努力走下去。

也祝愿各位同学,都能找到自己心动的offer。

分享我在这次面试前所做的准备(刷题复习资料以及一些大佬们的学习笔记和学习路线),都已经整理成了电子文档

拿到字节跳动offer后,简历被阿里捞了起来,二面迎来了P9"盘问"

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

ass UseAnnotation13 {

@Test

public void test1() {

//AnnotatedElementUtils是spring提供的一个查找注解的工具类

System.out.println(AnnotatedElementUtils.getMergedAnnotation(UseAnnotation13.class, B1.class));

System.out.println(AnnotatedElementUtils.getMergedAnnotation(UseAnnotation13.class, A1.class));

}

}

@0:A1注解value参数值默认为a

@1:B1注解上使用到了@A1注解

@2:B1注解value参数值默认为b

@2:UseAnnotation13上面使用了@B1注解,value参数的值为:路人甲java

test1方法中使用到了spring中的一个类AnnotatedElementUtils,通过这个工具类可以很方便的获取注解的各种信息,方法中的2行代码用于获取UseAnnotation13类上B1注解和A1注解的信息。

运行test1方法输出:

@com.javacode2018.lesson001.demo18.B1(value=路人甲Java)

@com.javacode2018.lesson001.demo18.A1(value=a)

上面用法很简单,没什么问题。

此时有个问题:此时如果想在UseAnnotation13上给B1上的A1注解设置值是没有办法的,注解定义无法继承导致的,如果注解定义上面能够继承,那用起来会爽很多,spring通过@Aliasfor方法解决了这个问题。

Spring  @AliasFor:对注解进行增强


直接上案例,然后解释代码。

案例1:通过@AliasFor解决刚才难题

感受:

其实我投简历的时候,都不太敢投递阿里。因为在阿里一面前已经过了字节的三次面试,投阿里的简历一直没被捞,所以以为简历就挂了。

特别感谢一面的面试官捞了我,给了我机会,同时也认可我的努力和态度。对比我的面经和其他大佬的面经,自己真的是运气好。别人8成实力,我可能8成运气。所以对我而言,我要继续加倍努力,弥补自己技术上的不足,以及与科班大佬们基础上的差距。希望自己能继续保持学习的热情,继续努力走下去。

也祝愿各位同学,都能找到自己心动的offer。

分享我在这次面试前所做的准备(刷题复习资料以及一些大佬们的学习笔记和学习路线),都已经整理成了电子文档

[外链图片转存中…(img-jKIOwmCB-1715414743581)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值