面试官没有直接问我“@Autowired和@Resource的区别”这种题,而是:
先让我举几个Spring注解的例子,再问“Spring注解定义为什么都挺短,不会有大量代码”,直接考察你对注解本质和Spring AOP/IOC底层的理解。
举几个Spring注解的例子
1. IOC依赖注入类注解
| @Autowired | @Qualifier 指定Bean名称;可以用 required=false 允许依赖为空 | |
| @Qualifier | @Autowired 使用,强制按名称(byName) 注入依赖 | |
| @Resource | name 属性显式指定Bean名称 |
2. IOC组件注册类注解
@Component | |
@Component | |
@Component | |
3. AOP切面类注解
这类注解主要是解决 “怎么在不修改业务代码的情况下,添加横切关注点” 的问题。
横切关注点就是“和业务逻辑无关,但很多地方都要用的功能”,比如日志、事务、权限校验、性能监控这些。
| 前置通知 | |
| 后置通知 | |
| 返回通知 | |
| 异常通知 | |
| 环绕通知 |
Lombok的常用注解
Lombok是一种代码简化工具,通过注解自动生成代码。
虽然不是Spring原生注解,但Lombok的注解是Java开发中经常会用到的。
| @Slf4j | |
| @Data | |
| @NoArgsConstructor | |
| @AllArgsConstructor |
Spring 注解定义为什么通常都挺短,不会有大量代码?
面试官这么问主要是想考察你对注解本质和Spring容器底层的理解。
先明确一下元数据(Metadata) 的定义,元数据就是“描述数据的数据”,本身不是数据的内容,只是给数据打标签、加属性。
根本原因在于注解只是元数据标记,本身不包含任何逻辑。
举个Spring注解的例子,比如 @Transactional:
// @Transactional的核心定义(简化版,去掉了很多元注解和属性)@Target({ElementType.METHOD, ElementType.TYPE}) // 元注解:标记这个注解能用在方法/类上@Retention(RetentionPolicy.RUNTIME) // 元注解:标记这个注解保留到运行时@Inherited// 元注解:标记这个注解可以被子类继承@Documented// 元注解:标记这个注解会被生成到JavaDoc里public@interface Transactional {// 只有属性,没有任何方法/逻辑!String value()default ""; // 事务管理器的名称Propagation propagation()default Propagation.REQUIRED; // 传播行为Isolation isolation()default Isolation.DEFAULT; // 隔离级别inttimeout()default -1; // 超时时间booleanreadOnly()defaultfalse; // 是否只读}可以看到@Transactional 的定义里只有属性,没有任何一行业务逻辑或容器逻辑。
它只是告诉Spring容器:“这个方法/类需要开启事务,传播行为是REQUIRED,隔离级别是DEFAULT...”,真正开启/提交/回滚事务的逻辑,全在Spring的事务管理器和AOP切面里。
除此之外,Spring注解的定义比较短,还有一个原因是Spring大量使用了元注解(Meta-Annotation)。
那什么是元注解呢?
元注解就是“标记注解的注解”,可以复用已有注解的功能,不用重复写代码。
比如上面的 @Transactional,它上面有@Target、@Retention、@Inherited、@Documented这4个元注解。
怎么自定义一个Spring注解?
简单来说有以下3步:
定义注解
用 @interface 定义,加上JDK原生元注解(至少 @Target 和 @Retention(RUNTIME));
编写后置处理器
实现 BeanPostProcessor 或 BeanFactoryPostProcessor 接口,或者用 AOP 切面(更适合方法级别的拦截,比如权限校验、记录执行时间),处理自定义注解的逻辑;
注册后置处理器
把自定义后置处理器交给Spring容器管理(比如用 @Component 标记)。
举个例子@AuthCheck:
@Target(ElementType.METHOD)//规定这个标签能贴在哪里,ElementType.METHOD 意思是“只能贴在方法上面”。@Retention(RetentionPolicy.RUNTIME)//规定这个标签的有效期,RUNTIME的意思是“程序运行的时候,这个标签依然存在”public@interface AuthCheck {/** * @interface 不是接口(interface),而是专门用来定义注解的语法。 * String mustRole():表示用这个标签时, * 你可以写上一个角色名,比如 @AuthCheck(mustRole = "admin")。 * default "":如果你只写了 @AuthCheck * 而没填角色,那默认值就是一个空字符串 "" * (表示不需要特定角色,只要登录就行,具体看你怎么实现业务逻辑)。 * */String mustRole()default "";}