面试官没有直接问我"@Autowired和@Resource的区别"这种题,而是:
先让我举几个Spring注解的例子,再问"Spring注解定义为什么都挺短,不会有大量代码",直接考察你对注解本质和Spring AOP/IOC底层的理解。
举几个Spring注解的例子
1. IOC依赖注入类注解
| 注解 | 作用 | 区别 |
|---|---|---|
| @Autowired | Spring原生注解,按类型(byType) 自动注入依赖 | 优先byType,找不到唯一Bean时,会尝试按变量名(byName) 匹配;可以配合 @Qualifier 指定Bean名称;可以用 required=false 允许依赖为空 |
| @Qualifier | 配合 @Autowired 使用,强制按名称(byName) 注入依赖 |
当容器中有多个同类型Bean时,必须用它指定具体用哪个 |
| @Resource | 按名称(byName) 优先注入,找不到再按类型(byType) 匹配 | JSR-250标准,跨框架兼容性好;可以用 name 属性显式指定Bean名称 |
2. IOC组件注册类注解
| 注解 | 作用 |
|---|---|
| @Component | 通用组件注册注解,把任意类标记为Spring容器的Bean |
| @Controller | @Component 的子类,专门标记Web控制器层的Bean |
| @Service | @Component 的子类,专门标记业务逻辑层的Bean |
| @Configuration | @Component 的子类,专门标记Java配置类,替代XML配置文件 |
| @Repository | @Component 的子类,专门标记数据访问层的 Bean;还能把数据库的异常(如 SQLException)转换成 Spring 的 DataAccessException(非受检异常) |
3. AOP切面类注解
横切关注点就是"和业务逻辑无关,但很多地方都要用的功能",比如日志、事务、权限校验、性能监控这些。
| 注解 | 作用 |
|---|---|
| @Aspect | 把类标记为切面类 |
| @Pointcut | 定义切入点表达式,告诉Spring"哪些方法需要被增强" |
| @Before | 前置通知 :在目标方法执行之前执行增强逻辑 |
| @After | 后置通知 :在目标方法执行之后(不管有没有异常)执行增强逻辑 |
| @AfterReturning | 返回通知 :在目标方法正常返回之后执行增强逻辑 |
| @AfterThrowing | 异常通知 :在目标方法抛出异常之后执行增强逻辑 |
| @Around | 环绕通知 :最强大的通知,可以完全控制目标方法的执行(比如不执行目标方法、修改参数、修改返回值) |
Lombok的常用注解
Lombok是一种代码简化工具,通过注解自动生成代码。
虽然不是Spring原生注解,但Lombok的注解是Java开发中经常会用到的。
| 注解 | 作用 |
|---|---|
| @Slf4j | 自动生成 SLF4J日志对象 |
| @Data | 自动生成 getter/setter、toString、equals、hashCode、无参构造器 |
| @NoArgsConstructor | 自动生成 无参构造器 |
| @AllArgsConstructor | 自动生成 全参构造器 |
Spring 注解定义为什么通常都挺短,不会有大量代码?
面试官这么问主要是想考察你对注解本质和Spring容器底层的理解。
先明确一下元数据(Metadata) 的定义,元数据就是"描述数据的数据",本身不是数据的内容,只是给数据打标签、加属性。
根本原因在于注解只是元数据标记,本身不包含任何逻辑。
举个Spring注解的例子,比如 @Transactional:
java
// @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; // 隔离级别
int timeout() default -1; // 超时时间
boolean readOnly() default false; // 是否只读
}
可以看到@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:
java
@Target(ElementType.METHOD)
//规定这个标签能贴在哪里,ElementType.METHOD 意思是"只能贴在方法上面"。
@Retention(RetentionPolicy.RUNTIME)
//规定这个标签的有效期,RUNTIME的意思是"程序运行的时候,这个标签依然存在"
public @interface AuthCheck {
/**
* @interface 不是接口(interface),而是专门用来定义注解的语法。
* String mustRole():表示用这个标签时,
* 你可以写上一个角色名,比如 @AuthCheck(mustRole = "admin")。
* default "":如果你只写了 @AuthCheck
* 而没填角色,那默认值就是一个空字符串 ""
* (表示不需要特定角色,只要登录就行,具体看你怎么实现业务逻辑)。
*
*/
String mustRole() default "";
}