关于注解的面试题

13 篇文章 0 订阅

金三银四,三四月是找工作最好的时期。错过了三月千万别放弃四月。

在面试的时候,有些面试官会问注解相关的问题, 注解最典型的代表框架就是Spring了,特别是Spring Boot出来之后,用注解代替了XML的配置,非常方便,今天我们就来聊聊注解相关的面试回答。

面试官的问法可能千奇百怪,我在这边总结几个常见的问题:
注解是什么?

注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。

简单来说注解其实就是代码中的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相对应的处理。
JDK内置了哪些注解?

    Overried
    Overried是告诉编译器要检查该方法是实现父类的方法。

    Deprecated
    Deprecated用于标记一些过时的代码。

    SuppressWarnings
    SuppressWarnings用于消除一些警告信息,使用集合的时候,如果没有指定泛型,IDE会提示安全检查的警告。

    FunctionalInterface
    FunctionalInterface是JDK8中的注解,用来指定该接口是函数式接口。

    SafeVarargs
    SafeVarargs是JDK 7中的注解,主要目的是处理可变长参数中的泛型,此注解告诉编译器:在可变长参数中的泛型是类型安全的。

怎么自定义一个注解?

在Java中,类使用class定义,接口使用interface定义,注解和接口的定义差不多,增加了一个@符号,即@interface,代码如下:

public @interface EnableAuth {

}

    1
    2
    3
    4

注解中可以定义成员变量,用于信息的描述,跟接口中方法的定义类似,代码如下:

public @interface EnableAuth {
    String name();
}

    1
    2
    3

还可以添加默认值:

public @interface EnableAuth {
    String name() default "猿天地";
}

    1
    2
    3

上面的介绍只是完成了自定义注解的第一步,开发中日常使用注解大部分是用在类上,方法上,字段上,示列代码如下:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EnableAuth {

}

    1
    2
    3
    4
    5
    6

    Target
    用于指定被修饰的注解修饰哪些程序单元,也就是上面说的类,方法,字段

    Retention
    用于指定被修饰的注解被保留多长时间,分别SOURCE(注解仅存在于源码中,在class字节码文件中不包含),CLASS(默认的保留策略,注解会在class字节码文件中存在,但运行时无法获取),RUNTIME(注解会在class字节码文件中存在,在运行时可以通过反射获取到)三种类型,如果想要在程序运行过程中通过反射来获取注解的信息需要将Retention设置为RUNTIME

    Documented
    用于指定被修饰的注解类将被javadoc工具提取成文档

    Inherited
    用于指定被修饰的注解类将具有继承性

如何获取注解中的值?

可以通过反射来判断类,方法,字段上是否有某个注解以及获取注解中的值, 获取某个类中方法上的注解代码示例如下:

Class<?> clz = bean.getClass();
Method[] methods = clz.getMethods();
for (Method method : methods) {
    if (method.isAnnotationPresent(EnableAuth.class)) {
        String name = method.getAnnotation(EnableAuth.class).name();
    }
}

    1
    2
    3
    4
    5
    6
    7

通过isAnnotationPresent判断是否存在某个注解,通过getAnnotation获取注解对象,然后获取值
工作中经常接触的注解有哪些?

注解在很多框架中都应用非常多,这个问题你可以说下Spring中的即可,大概的解释下每个注解的含义和用途,除了Spring还有很多别的框架中也有使用注解,比如Swagger, Lombok,JPA,Spring Data等

    Component
    Controller
    Repository
    Service
    RequestMapping
    RequestParam
    RequestAttribute
    RequestBody
    ResponseBody
    ……

注解的使用场景?

    生成文档
    Swagger中就是通过注解对接口,实体类中的字段进行描述生成可视化的文档
    代替配置文件
    Spring中Bean的装载注入
    导出数据
    可以写一个统一的导出工具类,传入一个List<实体类>进去即可导出Excel文件,Excel的表头可以用注解加载字段上
    框架层面的统一处理
    注解在底层框架中用的比较多,在框架中需要考虑到通用性,能用注解做很多事情,比如对API进行权限控制,限流等操作都可以通过自定义注解来标识是否需要进行认证,限流等,还有数据的缓存,典型的就是@Cacheable,还有异步方法的调用@Async,ORM框架中的使用,可以用注解标识表名,字段名,JPA中,Spring Data框架中都有使用

权限控制详细讲解

比如我们有的接口需要认证才能调用,有的不需要,简单的做法就是用配置的方式,将需要认证的接口配置好,然后进行拦截过滤,缺点是需要经常维护配置信息,用注解可以避免这个情况。
可以自定义一个注解,只要加了这个注解我们就对这个接口进行认证拦截操作,接下里详细的讲解下这个功能实现。

定义开启认证的注解,作用在方法上,运行时可获取注解信息


/**
 * 开启API权限认证
 * @author yinjihuan
 *
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EnableAuth {

}

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

在需要认证的接口上增加注解

@EnableAuth
@GetMapping("/userCollectCityInfo")
@ApiOperation(value="获取登录用户关注的城市信息", notes="获取登录用户关注的城市信息", produces = "application/json")
@ApiResponses(
        @ApiResponse(response = UserCollectCityInfoDto.class, code = 200, message = "")
)
public Response getUserCollectCityInfos(HttpServletRequest request) {
    try {
        Long uid = UserInfoUtils.getLoginUserId(request);
        List<CityCollect> citys = cityCollectService.findAllByUid(uid);
        List<UserCollectCityInfoDto> results = citys.stream().map(this::ofCityInfo)
                    .sorted((l1, l2) -> l1.getRangeLevel().compareTo(l2.getRangeLevel()))
                    .collect(Collectors.toList());
        return Response.ok(results);
    } catch (Exception e) {
        logger.error("获取登录用户关注的城市信息异常", e);
        return Response.fail("获取登录用户关注的城市信息异常");
    }
}

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19

在拦截器中进行拦截,拦截需要知道当前请求的接口是不是需要拦截的,我们可以在启动时将所有增加了@EnableAuth的接口信息保存起来,这样在拦截器中就知道哪个接口是需要认证。

初始化需要认证的接口信息代码如下:

/**
 * API 验证数据初始化
 * @author yinjihuan
 *
 */
@Component
@Configuration
public class ApiAuthDataInit implements ApplicationContextAware {

    public static List<String> checkApis = new ArrayList<String>();

    @Override
    public void setApplicationContext(ApplicationContext ctx) throws BeansException {
        Map<String, Object> beanMap = ctx.getBeansWithAnnotation(RestController.class);
        if (beanMap != null) {
            for (Object bean : beanMap.values()) {
                Class<?> clz = bean.getClass();
                Method[] methods = clz.getMethods();
                for (Method method : methods) {
                    if (method.isAnnotationPresent(EnableAuth.class)) {
                        String uri = getApiUri(clz, method);
                        checkApis.add(uri);
                    }
                }
            }
        }
    }

    private String getApiUri(Class<?> clz, Method method) {
        StringBuilder uri = new StringBuilder();
        uri.append(clz.getAnnotation(RequestMapping.class).value()[0]);
        if (method.isAnnotationPresent(GetMapping.class)) {
            uri.append(method.getAnnotation(GetMapping.class).value()[0]);
        } else if (method.isAnnotationPresent(PostMapping.class)) {
            uri.append(method.getAnnotation(PostMapping.class).value()[0]);
        } else if (method.isAnnotationPresent(RequestMapping.class)) {
            uri.append(method.getAnnotation(RequestMapping.class).value()[0]);
        }
        return uri.toString();
    }

}

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42

实现ApplicationContextAware接口,然后通过getBeansWithAnnotation获取所有接口的bean信息,通过RestController注解来获取,也就是说只要class上增加了RestController注解,这边就都能获取到。

然后通过反射获取bean中所有的方法,如果有增加EnableAuth的话就获取接口的uri存储到map中,这样过滤器中就可以根据map中的值来判断是不是需要进行权限认证了。

更多技术分享请关注微信公众号:猿天地
猿天地微信公众号
--------------------- 
作者:尹吉欢 
来源:CSDN 
原文:https://blog.csdn.net/u010889990/article/details/80333100 
版权声明:本文为博主原创文章,转载请附上博文链接!

 

 

===================================================================================

1.简介

自Java 5以来,注释已经出现,而现在,它们是无处不在的编程结构,可以丰富代码。

在本文中,我们将回顾一些关于注释的问题; 通常会在技术面谈时询问,并在适当情况下; 我们将实施示例以更好地理解他们的答案。

2.问题

Q1。什么是注释?他们的典型用例是什么?

注释是绑定到程序源代码元素的元数据,对它们运行的​​代码的操作没有影响。

他们的典型用例是:

  • 编译器的信息 - 使用注释,编译器可以检测错误或抑制警告
  • 编译时和部署时处理 - 软件工具可以处理注释并生成代码,配置文件等。
  • 运行时处理 - 可以在运行时检查注释以自定义程序的行为

Q2。从标准库中描述一些有用的注释。

java.langjava.lang.annotation包中有几个注释,更常见的包括但不限于:

  • @Override -标记方法是否覆盖超类中声明的元素。如果它无法正确覆盖该方法,编译器将发出错误
  • @Deprecated - 表示该元素已弃用且不应使用。如果程序使用标有此批注的方法,类或字段,编译器将发出警告
  • @SuppressWarnings - 告诉编译器禁止特定警告。在与泛型出现之前编写的遗留代码接口时最常用的
  • @FunctionalInterface - 在Java 8中引入,表明类型声明是一个功能接口,可以使用Lambda Expression提供其实现

Q3。你怎么能创建一个注释?

注释是一种接口形式,其中关键字接口@开头,其主体包含与方法非常相似的注释类型元素声明:

 
  1.  
  2.  
  3. public @interface SimpleAnnotation {

  4.  
  5.     String value();

  6.  
  7.  
  8.  
  9.     int[] types();

  10.  
  11. }

 

 

定义注释后,yon可以通过代码开始使用它:

 
  1. @SimpleAnnotation(value = "an element", types = 1)

  2.  
  3. public class Element {

  4.  
  5.     @SimpleAnnotation(value = "an attribute", types = { 1, 2 })

  6.  
  7.     public Element nextElement;

  8.  
  9. }

请注意,为数组元素提供多个值时,必须将它们括在括号中。

可选地,只要它们是编译器的常量表达式,就可以提供默认值:

 
  1. public @interface SimpleAnnotation {

  2.  
  3.     String value() default "This is an element";

  4.  
  5.  
  6.  
  7.     int[] types() default { 1, 2, 3 };

  8.  
  9. }

现在,您可以使用没有这些元素的注释:

 
  1. @SimpleAnnotation

  2.  
  3. public class Element {

  4.  
  5.     // ...

  6.  
  7. }

或者只是其中一些:

 
  1. @SimpleAnnotation(value = "an attribute")

  2.  
  3. public Element nextElement;

Q4。可以从注释方法声明返回哪些对象类型?

返回类型必须是基本类型,StringClassEnum或以前类型之一的数组。否则,编译器将抛出错误。

这是一个成功遵循这个原则的示例代码:

 
  1. enum Complexity {

  2.  
  3.     LOW, HIGH

  4.  
  5. }

  6.  
  7.  
  8.  
  9. public @interface ComplexAnnotation {

  10.  
  11.     Class<? extends Object> value();

  12.  
  13.  
  14.  
  15.     int[] types();

  16.  
  17.  
  18.  
  19.     Complexity complexity();

  20.  
  21. }

由于Object不是有效的返回类型,下一个示例将无法编译:

 
  1. public @interface FailingAnnotation {

  2.  
  3.     Object complexity();

  4. }

Q5。哪些程序元素可以注释?

注释可以应用于整个源代码的多个位置。它们可以应用于类,构造函数和字段的声明:

 
  1. @SimpleAnnotation

  2.  
  3. public class Apply {

  4.  
  5.     @SimpleAnnotation

  6.  
  7.     private String aField;

  8.  
  9.  
  10.     @SimpleAnnotation

  11.  
  12.     public Apply() {

  13.  
  14.         // ...

  15.  
  16.     }

  17.  
  18. }

方法及其参数:

 
  1. @SimpleAnnotation

  2.  
  3. public void aMethod(@SimpleAnnotation String param) {

  4.  
  5.     // ...

  6.  
  7. }

局部变量,包括循环和资源变量:

 
  1. @SimpleAnnotation

  2.  
  3. int i = 10;

  4.  
  5.  
  6.  
  7. for (@SimpleAnnotation int j = 0; j < i; j++) {

  8.  
  9.     // ...

  10.  
  11. }

  12.  
  13.  
  14.  
  15. try (@SimpleAnnotation FileWriter writer = getWriter())

  16.  
  17.     // ...

  18.  
  19. } catch (Exception ex) {

  20.  
  21.     // ...

  22.  
  23. }

其他注释类型:

 
  1. @SimpleAnnotation

  2.  
  3. public @interface ComplexAnnotation {

  4.  
  5.     // ...

  6.  
  7. }

甚至包,通过package-info.java文件:

 
  1. @PackageAnnotation

  2.  
  3. package com.baeldung.interview.annotations;

从Java 8开始,它们也可以应用于类型的使用。为此,注释必须指定值为ElementType.USE@Target注释:

 
  1. @Target(ElementType.TYPE_USE)

  2.  
  3. public @interface SimpleAnnotation {

  4.  
  5.     // ...

  6.  
  7. }

现在,注释可以应用于类实例创建:

new @SimpleAnnotation Apply();

类型转换:

 
  1. aString = (@SimpleAnnotation String) something;

  2.  

实施条款:

 
  1. public class SimpleList<T>

  2.  
  3.   implements @SimpleAnnotation List<@SimpleAnnotation T> {

  4.  
  5.     // ...

  6.  
  7. }

抛出条款:

	void aMethod() throws @SimpleAnnotation Exception {   // ...>}

Q6。有没有办法限制可以应用注释的元素?

是的,@ Target注释可用于此目的。如果我们尝试在不适用的上下文中使用注释,编译器将发出错误。

以下是仅将@SimpleAnnotation批注的用法限制为字段声明的示例:

 
  1. @Target(ElementType.FIELD)

  2.  
  3. public @interface SimpleAnnotation {

  4.  
  5.     // ...

  6.  
  7. }

如果我们想让它适用于更多的上下文,我们可以传递多个常量:

 
  1.  
  2. @Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PACKAGE })

我们甚至可以制作一个注释,因此它不能用于注释任何东西。当声明的类型仅用作复杂注释中的成员类型时,这可能会派上用场:

 
  1. @Target({})

  2.  
  3. public @interface NoTargetAnnotation {

  4.  
  5.     // ...

  6.  
  7. }

Q7。什么是元注释?

是否适用于其他注释的注释。

所有未标记为@Target的注释或者使用它标记但包含ANNOTATION_TYPE常量的注释也都是元注释:

 
  1. @Target(ElementType.ANNOTATION_TYPE)

  2.  
  3. public @interface SimpleAnnotation {

  4.  
  5.     // ...

  6.  
  7. }

Q8。什么是重复注释?

这些是可以多次应用于同一元素声明的注释。

出于兼容性原因,由于此功能是在Java 8中引入的,因此重复注释存储在由Java编译器自动生成的容器注释中。对于编译器来说,执行此操作有两个步骤来声明它们。

首先,我们需要声明一个可重复的注释:

 
  1. @Repeatable(Schedules.class)

  2.  
  3. public @interface Schedule {

  4.  
  5.     String time() default "morning";

  6.  
  7. }

然后,我们使用强制元素定义包含注释,其类型必须是可重复注释类型的数组:

 
  1. public @interface Schedules {

  2.  
  3.     Schedule[] value();

  4. }

现在,我们可以多次使用@Schedule:

 
  1. @Schedule

  2.  
  3. @Schedule(time = "afternoon")

  4.  
  5. @Schedule(time = "night")

  6.  
  7. void scheduledMethod() {

  8.  
  9.     // ...

  10.  
  11. }

Q9。你怎么能检索注释?这与保留政策有何关系?

您可以使用Reflection API或注释处理器来检索注释。

@Retention注解和其的RetentionPolicy参数会影响您检索它们。RetentionPolicy枚举中有三个常量:

  • RetentionPolicy.SOURCE - 使注释被编译器丢弃,但注释处理器可以读取它们
  • RetentionPolicy.CLASS - 表示注释已添加到类文件中,但无法通过反射访问
  • RetentionPolicy.RUNTIME -Annotations由编译器记录在类文件中,并由JVM在运行时保留,以便可以反射性地读取它们

这是一个用于创建可在运行时读取的注释的示例代码:

 
  1. @Retention(RetentionPolicy.RUNTIME)

  2.  
  3. public @interface Description {

  4.  
  5.     String value();

  6.  
  7. }

现在,可以通过反射检索注释:

 
  1. Description description

  2.  
  3.   = AnnotatedClass.class.getAnnotation(Description.class);

  4.  
  5. System.out.println(description.value());

注释处理器可以与RetentionPolicy.SOURCE一起使用,这在Java Annotation Processing和Creating a Builder一文中有所描述。

当您编写Java字节码解析器时,RetentionPolicy.CLASS可用。

Q10。下面的代码会编译吗?

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

  2.  
  3. public @interface TestAnnotation {

  4.  
  5.     int[] value() default {};

  6.  
  7. }

不。如果在@Target注释中多次出现相同的枚举常量,那么这是一个编译时错误。

删除重复常量将使代码成功编译:

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

Q11。是否可以扩展注释?

注释。注释总是扩展java.lang.annotation.Annotation,Java语言规范中所述

如果我们尝试在注释声明中使用extends子句,我们将得到一个编译错误:

 
  1. public @interface AnAnnotation extends OtherAnnotation {

  2.    // Compilation error}

结论

在本文中,我们介绍了Java开发人员关于注释的技术访谈中出现的一些常见问题。这绝不是一份详尽的清单,只应被视为进一步研究的开始。

在Baeldung,我们祝愿您在即将进行的任何采访中取得成功。

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值