Java注解详解

O泡李华 8

Java 注解详解

本章位置:第二阶段 Java 核心框架
前置知识:反射机制、内省机制、JavaBean
本章内容:Java Annotation 注解
下一篇:Lambda 表达式
学习目标:理解注解是什么、注解本质、内置注解、自定义注解、元注解、注解属性、运行时读取方式,并理解注解在 JUnit、Spring、SpringBoot、MyBatis 等框架中的作用。


一、什么是注解

注解:

Annotation

可以理解为:

给类、方法、字段、参数等程序元素附加额外说明信息,也就是“元数据”。

例如:

@Override
public String toString() {

    return "...";
}

这里:

@Override

就是一个注解。

它没有直接改变:

toString 方法的业务逻辑

而是在告诉:

编译器
工具
框架

一些额外信息。


二、注解不是注释

注释:

// 这是一个用户类

主要给:

人看

注解:

@Service
public class UserService {

}

主要可以给:

编译器
JVM
框架
工具

读取和处理。

所以:

注释
偏向人类说明

注解
偏向程序元数据

三、为什么需要注解

假设没有注解,

框架需要知道:

哪个类是 Service

哪个方法是测试方法

哪个字段需要注入

哪个接口映射某个 URL

可能只能:

写 XML

写配置文件

写大量约定代码

注解让这些信息可以直接:

写在代码附近

例如:

@Test
void testAdd() {

}

JUnit 看到:

@Test

就知道:

这是一个测试方法

四、常见注解

你已经见过很多:

@Override

@Deprecated

@SuppressWarnings

@Test

@Service

@Controller

@RestController

@Autowired

@RequestMapping

它们都属于:

注解

五、注解本身会自动执行功能吗

不会。

这是非常重要的一点。

例如:

@MyTest
public void hello() {

}

如果:

没有任何程序读取 @MyTest

那它通常不会自动做任何事情。

所以注解真正生效需要:

编译器处理

框架扫描

反射读取

注解处理器处理

六、注解和反射的关系

例如:

@Service
public class UserService {

}

Spring 大致会:

扫描类
↓
通过反射检查有没有 @Service
↓
如果有
创建对象
↓
加入 IOC 容器

所以:

注解负责“标记”,反射负责“读取和执行”。


七、注解的定义语法

自定义注解使用:

@interface

例如:

public @interface MyAnnotation {

}

注意:

不是 interface

而是:

@interface

八、最简单的自定义注解

public @interface MyTest {

}

使用:

@MyTest
public void test() {

}

九、注解可以放在哪里

取决于:

@Target

可以放在:

类

方法

字段

构造方法

参数

局部变量

包

模块

类型使用位置

十、什么是元注解

元注解:

用来修饰“注解”的注解。

常见:

@Target

@Retention

@Documented

@Inherited

@Repeatable

十一、@Target

用于指定:

注解可以写在哪里

例如:

@Target(
        ElementType.METHOD
)
public @interface MyTest {

}

表示:

MyTest 只能写在方法上

十二、ElementType

常见:

TYPE
类、接口、枚举、注解

FIELD
字段

METHOD
方法

PARAMETER
参数

CONSTRUCTOR
构造方法

LOCAL_VARIABLE
局部变量

ANNOTATION_TYPE
注解类型

PACKAGE
包

TYPE_PARAMETER
类型参数

TYPE_USE
类型使用位置

十三、多个 Target

例如:

@Target({
        ElementType.TYPE,
        ElementType.METHOD
})
public @interface MyAnnotation {

}

表示:

可以放类上

也可以放方法上

十四、@Retention

用于指定:

注解保留到什么时候

常见:

SOURCE

CLASS

RUNTIME

十五、RetentionPolicy.SOURCE

@Retention(
        RetentionPolicy.SOURCE
)

表示:

只存在源代码阶段

编译后通常不会保留。

典型:

@Override

主要由编译器使用。


十六、RetentionPolicy.CLASS

@Retention(
        RetentionPolicy.CLASS
)

表示:

会进入 .class 文件

但运行时:

不一定能通过反射读取

这是默认策略。


十七、RetentionPolicy.RUNTIME

@Retention(
        RetentionPolicy.RUNTIME
)

表示:

运行时仍然保留

可以通过:

反射

读取。

框架注解通常非常常见:

RUNTIME

十八、为什么框架注解常用 RUNTIME

例如:

@Service

Spring 启动时需要:

在程序运行期间
判断类有没有 @Service

所以必须:

运行时保留

十九、@Documented

例如:

@Documented
public @interface MyAnnotation {

}

表示:

生成 Javadoc 时
希望这个注解也出现在文档中

二十、@Inherited

@Inherited

表示:

某些类级别注解
可以被子类继承

注意:

只对类继承语义有特定作用

并不是:

方法、字段、接口上的注解全部自动继承

二十一、@Repeatable

允许:

同一个注解重复使用

例如:

@Role("ADMIN")
@Role("USER")
public class User {

}

需要配合:

@Repeatable

定义容器注解。


二十二、自定义注解完整例子

import java.lang.annotation.*;

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

}

使用:

public class Demo {

    @MyTest
    public void testAdd() {

        System.out.println(
                "执行测试"
        );
    }
}

二十三、注解属性

注解中可以定义:

属性

例如:

public @interface MyTest {

    String value();
}

使用:

@MyTest(
        value = "加法测试"
)
public void testAdd() {

}

二十四、value 特殊规则

如果注解只有一个:

value

属性,

可以省略:

value =

例如:

@MyTest(
        "加法测试"
)

二十五、多个属性

public @interface ApiInfo {

    String name();

    String description();

    int version();
}

使用:

@ApiInfo(
        name = "用户接口",
        description = "用户管理",
        version = 1
)
public class UserController {

}

二十六、默认值 default

public @interface ApiInfo {

    String name();

    int version()
            default 1;
}

使用:

@ApiInfo(
        name = "用户接口"
)

version 自动:

1

二十七、注解属性允许哪些类型

注解属性常见允许:

基本类型

String

Class

enum

Annotation

以上类型的一维数组

二十八、String 属性

String value();

二十九、Class 属性

Class<?> type();

使用:

@Handler(
        type =
                UserService.class
)

三十、enum 属性

定义:

public enum Level {

    LOW,
    MEDIUM,
    HIGH
}

注解:

public @interface Risk {

    Level level();
}

使用:

@Risk(
        level =
                Level.HIGH
)

三十一、数组属性

public @interface Roles {

    String[] value();
}

使用:

@Roles({
        "ADMIN",
        "USER"
})

三十二、单个数组元素可以省略大括号吗

如果只有一个:

@Roles(
        "ADMIN"
)

也是可以的。


三十三、内置注解:@Override

@Override

告诉编译器:

这个方法应该重写父类或接口方法

三十四、@Override 的价值

如果父类方法:

public void show() {

}

你误写:

@Override
public void shwo() {

}

编译器会:

直接报错

如果没有 @Override,

你可能误以为:

已经重写

实际却创建了新方法。


三十五、建议重写方法都加 @Override

因为它可以:

提高可读性

帮助编译器检查

减少拼写错误

三十六、@Deprecated

标记:

过时 API

例如:

@Deprecated
public void oldMethod() {

}

调用时 IDE 会:

警告

三十七、@Deprecated 不等于不能用

它通常表示:

不推荐继续使用

未来可能删除

有更好的替代方案

但:

当前可能仍然能运行

三十八、@SuppressWarnings

用于:

抑制编译器警告

例如:

@SuppressWarnings(
        "unchecked"
)

三十九、不要滥用 @SuppressWarnings

错误:

一有黄色警告
就全部 suppress

更应该先:

理解警告原因

只有确认合理时再抑制。


四十、@SafeVarargs

用于:

泛型可变参数

相关安全警告。

当前阶段:

了解即可

四十一、@FunctionalInterface

用于标记:

函数式接口

例如:

@FunctionalInterface
public interface Calculator {

    int calculate(
            int a,
            int b
    );
}

四十二、函数式接口

函数式接口:

只有一个抽象方法

这样才能:

使用 Lambda

下一篇会详细讲。


四十三、反射读取类注解

假设:

@Retention(
        RetentionPolicy.RUNTIME
)
@Target(
        ElementType.TYPE
)
public @interface Component {

    String value()
            default "";
}

使用:

@Component(
        "userService"
)
public class UserService {

}

四十四、isAnnotationPresent()

boolean present =
        UserService.class
                .isAnnotationPresent(
                        Component.class
                );

结果:

true

四十五、getAnnotation()

Component component =
        UserService.class
                .getAnnotation(
                        Component.class
                );

读取属性:

String value =
        component.value();

四十六、getAnnotations()

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

获取:

当前可见注解

四十七、getDeclaredAnnotations()

Annotation[] annotations =
        UserService.class
                .getDeclaredAnnotations();

主要关注:

当前元素自己声明的注解

四十八、反射读取方法注解

Method method =
        UserService.class
                .getDeclaredMethod(
                        "save"
                );

boolean present =
        method
                .isAnnotationPresent(
                        MyLog.class
                );

四十九、读取字段注解

Field field =
        User.class
                .getDeclaredField(
                        "username"
                );

MyField annotation =
        field
                .getAnnotation(
                        MyField.class
                );

五十、读取参数注解

方法:

public void login(
        @NotEmpty
        String username
) {

}

可以通过:

Parameter

反射读取参数注解。


五十一、Parameter

例如:

Method method =
        UserService.class
                .getMethod(
                        "login",
                        String.class
                );

Parameter[] parameters =
        method
                .getParameters();

五十二、参数是否有注解

for (
    Parameter parameter :
    parameters
) {

    boolean present =
            parameter
                    .isAnnotationPresent(
                            NotEmpty.class
                    );
}

五十三、综合案例:自定义测试框架

定义:

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

}

五十四、测试类

public class CalculatorTest {

    @MyTest
    public void testAdd() {

        System.out.println(
                "testAdd 执行"
        );
    }

    public void helper() {

    }

    @MyTest
    public void testSubtract() {

        System.out.println(
                "testSubtract 执行"
        );
    }
}

五十五、扫描测试方法

Class<
        CalculatorTest
        > clazz =
        CalculatorTest.class;

CalculatorTest testObject =
        clazz
                .getDeclaredConstructor()
                .newInstance();

for (
    Method method :
    clazz.getDeclaredMethods()
) {

    if (
        method
                .isAnnotationPresent(
                        MyTest.class
                )
    ) {

        method.invoke(
                testObject
        );
    }
}

五十六、这个案例说明了什么

我们已经做出了:

最简版 JUnit 思想

过程:

定义 @MyTest
↓
标记测试方法
↓
反射扫描方法
↓
发现注解
↓
invoke()

JUnit 的真实实现当然复杂很多,

但核心思路很类似。


五十七、综合案例:自定义 IOC 标记

定义:

@Target(
        ElementType.TYPE
)
@Retention(
        RetentionPolicy.RUNTIME
)
public @interface MyComponent {

    String value()
            default "";
}

五十八、业务类

@MyComponent(
        "userService"
)
public class UserService {

}

五十九、框架扫描思路

如果程序扫描到:

UserService.class

可以:

if (
    clazz.isAnnotationPresent(
            MyComponent.class
    )
) {

    Object bean =
            clazz
                    .getDeclaredConstructor()
                    .newInstance();
}

然后放入:

Map<String, Object>

这就是:

最简 IOC 注解扫描思想

六十、注解 + 反射 + IOC

可以总结:

注解
负责告诉框架
“这个类有什么身份”

反射
负责读取注解并创建对象

IOC 容器
负责保存对象

六十一、综合案例:字段自动注入

定义:

@Target(
        ElementType.FIELD
)
@Retention(
        RetentionPolicy.RUNTIME
)
public @interface MyAutowired {

}

六十二、使用注解

public class UserService {

    @MyAutowired
    private UserDao userDao;
}

六十三、反射扫描字段

for (
    Field field :
    clazz.getDeclaredFields()
) {

    if (
        field
                .isAnnotationPresent(
                        MyAutowired.class
                )
    ) {

        field.setAccessible(
                true
        );

        // 从 IOC 容器中找到对应对象

        Object dependency =
                container.get(
                        field
                                .getType()
                );

        field.set(
                bean,
                dependency
        );
    }
}

六十四、这就是 @Autowired 的基础思想

Spring 真实实现复杂很多,

但核心可以理解:

扫描字段
↓
发现 @Autowired
↓
判断字段类型
↓
从容器查找 Bean
↓
反射注入

六十五、综合案例:方法权限注解

定义:

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

    String value();
}

六十六、使用权限注解

@RequireRole(
        "ADMIN"
)
public void deleteUser() {

}

六十七、框架读取权限

RequireRole annotation =
        method.getAnnotation(
                RequireRole.class
        );

String role =
        annotation.value();

然后判断:

当前用户是否有 ADMIN

六十八、这就是声明式编程思想

业务方法:

@RequireRole(
        "ADMIN"
)
public void deleteUser() {

}

方法本身不用手动写:

if 用户没有权限

而是:

把规则声明在注解上

由框架统一处理。

这叫:

声明式编程

六十九、Spring 为什么大量使用注解

因为注解可以表达:

组件身份

依赖关系

请求映射

事务

权限

参数校验

配置绑定

例如:

@Service

@Autowired

@GetMapping

@Transactional

@Validated

七十、常见 Spring 注解预览

@Component

@Service

@Repository

@Controller

@RestController

@Autowired

@RequestMapping

@GetMapping

@PostMapping

@Transactional

后面对应章节再详细学习。


七十一、JUnit 注解预览

@Test

@BeforeEach

@AfterEach

@BeforeAll

@AfterAll

@Disabled

本质都是:

给测试框架提供元数据

七十二、MyBatis 注解预览

例如:

@Select(
        "SELECT * FROM user"
)

或者:

@Mapper

都是:

注解驱动配置

七十三、注解和 XML 的关系

过去框架大量使用:

XML

例如:

<bean
    id="userService"
    class="..."
/>

现在很多信息可以写:

@Service

七十四、注解的优点

靠近代码

阅读方便

减少部分 XML

类型信息更直接

开发效率高

七十五、注解的缺点

如果滥用:

类上十几个注解

方法上十几个注解

也可能:

难理解

隐式行为太多

框架魔法感强

所以:

注解不是越多越好

七十六、自定义注解命名

建议:

表达意图

例如:

@RequiresLogin

@AuditLog

@RateLimit

@Sensitive

@Cacheable

不要:

@A

@Test1

@Flag

这种含义模糊的名字。


七十七、注解属性设计原则

尽量:

简单

明确

默认值合理

不要塞太多配置

例如:

@RateLimit(
        permits = 10,
        seconds = 1
)

很直观。


七十八、运行时注解必须有 RUNTIME

如果你要:

clazz.getAnnotation(...)

读取,

自定义注解必须:

@Retention(
        RetentionPolicy.RUNTIME
)

否则运行时可能读不到。


七十九、常见错误:忘记 @Retention

默认:

RetentionPolicy.CLASS

如果你想运行时反射:

必须明确 RUNTIME

八十、常见错误:Target 不匹配

例如:

@Target(
        ElementType.METHOD
)

然后写:

@MyTest
public class Demo {

}

会:

编译报错

因为只能用于:

方法

八十一、常见错误:把注解当函数调用

例如:

@MyLog
public void save() {

}

不会自动:

打印日志

必须有框架:

扫描 + 拦截 + 执行逻辑

八十二、常见错误:value 写法不理解

定义:

String value();

使用可以:

@Role(
        value = "ADMIN"
)

也可以:

@Role(
        "ADMIN"
)

八十三、多个属性不能乱省略

如果:

public @interface Api {

    String value();

    String method();
}

必须:

@Api(
        value = "/users",
        method = "GET"
)

不能:

@Api(
        "/users",
        "GET"
)

注解不支持这种位置参数写法。


八十四、常见错误:数组值写法

定义:

String[] roles();

多个:

@Permission(
        roles = {
                "ADMIN",
                "USER"
        }
)

单个:

@Permission(
        roles = "ADMIN"
)

都可以。


八十五、常见错误:把 ordinal 当注解配置

如果注解需要状态,

更推荐:

Level.HIGH

而不是:

2

枚举更清晰。


八十六、重复注解

如果需要:

@Role("ADMIN")
@Role("USER")

需要设计:

@Repeatable

八十七、重复注解完整思路

容器:

@Retention(
        RetentionPolicy.RUNTIME
)
@Target(
        ElementType.TYPE
)
public @interface Roles {

    Role[] value();
}

单个:

@Retention(
        RetentionPolicy.RUNTIME
)
@Target(
        ElementType.TYPE
)
@Repeatable(
        Roles.class
)
public @interface Role {

    String value();
}

八十八、使用重复注解

@Role(
        "ADMIN"
)
@Role(
        "USER"
)
public class UserService {

}

八十九、读取重复注解

Role[] roles =
        UserService.class
                .getAnnotationsByType(
                        Role.class
                );

九十、组合注解思想

Spring 中很多注解可以:

注解上再加注解

形成:

组合注解

例如:

@RestController

本身就可以理解成多个语义组合。


九十一、元注解和组合注解的关系

如果定义:

@Target(
        ElementType.TYPE
)
@Retention(
        RetentionPolicy.RUNTIME
)
@Component
public @interface MyService {

}

框架如果支持:

元注解扫描

就可能把:

@MyService

也识别成组件。


九十二、注解继承问题

@Inherited 只对:

类

的继承起作用。

并且:

接口上的注解
不会因为实现接口
就自动通过 @Inherited 继承

这一点容易误解。


九十三、方法注解不会自动继承

父类方法:

@MyLog
public void save() {

}

子类重写:

@Override
public void save() {

}

子类新方法对象上:

不一定自动拥有 @MyLog

框架要根据规则自行处理。


九十四、注解代理对象

当你:

Component component =
        clazz.getAnnotation(
                Component.class
        );

拿到的对象本质上:

并不是你手动 new 出来的普通对象

JVM 会提供:

注解接口的运行时实现

你可以直接调用:

component.value();

九十五、注解本质上是一种特殊接口

定义:

public @interface MyTest {

}

编译后本质和:

Annotation 接口体系

有关。

所有注解类型都会:

间接实现 java.lang.annotation.Annotation

九十六、Annotation 接口

java.lang.annotation.Annotation

常见方法:

annotationType()

equals()

hashCode()

toString()

九十七、annotationType()

Annotation annotation =
        clazz.getAnnotation(
                Component.class
        );

Class<? extends Annotation>
        type =
        annotation
                .annotationType();

得到:

Component.class

九十八、运行时扫描的基本流程

框架处理注解常见:

扫描类
↓
获取 Class
↓
检查注解
↓
读取注解属性
↓
根据注解执行框架逻辑

九十九、一个简单路由注解

定义:

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

    String path();

    String method()
            default "GET";
}

一百、使用 Route

public class UserController {

    @Route(
            path = "/users",
            method = "GET"
    )
    public void list() {

    }

    @Route(
            path = "/users",
            method = "POST"
    )
    public void add() {

    }
}

一百零一、扫描路由

for (
    Method method :
    UserController.class
            .getDeclaredMethods()
) {

    Route route =
            method
                    .getAnnotation(
                            Route.class
                    );

    if (
        route != null
    ) {

        System.out.println(
                route.method()
                        + " "
                        + route.path()
                        + " → "
                        + method.getName()
        );
    }
}

一百零二、这和 Spring MVC 有什么关系

Spring MVC:

@GetMapping(
        "/users"
)
public List<User>
list() {

}

框架会:

扫描 Controller
↓
读取请求映射注解
↓
建立 URL 与 Method 的映射
↓
请求到来
↓
反射调用对应方法

一百零三、自定义日志注解

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

    String value();
}

使用:

@AuditLog(
        "删除用户"
)
public void deleteUser() {

}

一百零四、日志框架处理思路

方法执行前
↓
读取 @AuditLog
↓
得到“删除用户”
↓
记录操作日志
↓
执行目标方法

后面 AOP 就会非常适合做这种事情。


一百零五、注解和 AOP

很多功能可以写成:

注解
+
AOP

例如:

@Transactional

@Cacheable

@Async

@PreAuthorize

@AuditLog

一百零六、声明式事务

@Transactional
public void transfer() {

}

业务代码不需要手动:

begin
commit
rollback

Spring 根据注解:

自动管理事务

这就是:

注解驱动 + AOP

一百零七、编译期注解处理简单了解

注解不只是:

运行时反射

还可以在编译阶段处理。

例如:

Lombok
MapStruct
部分代码生成框架

会使用:

Annotation Processing

一百零八、Annotation Processor

编译器可以发现:

特定注解

然后执行:

注解处理器

生成:

Java 源码
元数据
辅助代码

一百零九、运行时反射和编译期处理区别

运行时注解
程序启动后读取
典型:Spring

编译期注解处理
编译时处理
典型:Lombok / MapStruct

一百一十、为什么 Lombok 不主要依赖运行时反射

例如:

@Getter
@Setter

Lombok 会在:

编译阶段

修改或生成相关代码。

所以运行后:

Getter / Setter 已经存在

一百一十一、注解的适用场景

适合:

声明组件

声明路由

权限要求

事务

日志

缓存

测试

参数校验

序列化规则

数据库映射

一百一十二、注解不适合承载大量业务数据

不要设计:

@Order(
        product1 = "...",
        product2 = "...",
        ...
)

注解更适合:

元数据
配置
规则标记

而不是:

复杂动态业务数据

一百一十三、练习题 1:自定义 @MyTest

要求:

只能作用于方法

运行时保留

使用反射:

执行所有 @MyTest 方法

一百一十四、练习题 2:@Component

定义:

@Component(
        "userService"
)

要求:

只能作用于类

运行时可读取

反射输出:

Bean 名称

一百一十五、练习题 3:@Column

定义:

@Column(
        "user_name"
)
private String username;

通过反射输出:

字段名 username

数据库列名 user_name

一百一十六、练习题 4:@Required

定义:

@Required
private String username;

反射校验:

字段值不能为 null

一百一十七、练习题 5:@Route

定义:

@Route(
        path = "/users",
        method = "GET"
)

扫描 Controller:

输出所有路由

一百一十八、练习题 6:@RequireRole

定义:

@RequireRole(
        "ADMIN"
)

模拟:

当前角色为 USER

如果无权限:

拒绝执行方法

一百一十九、练习题 7:默认值

定义:

@Retry(
        count = 3
)

其中:

count 默认 3
delay 默认 1000

读取注解属性。


一百二十、练习题 8:数组属性

定义:

@Permission(
        roles = {
                "ADMIN",
                "USER"
        }
)

遍历 roles。


一百二十一、练习题 9:重复注解

定义:

@Tag(
        "Java"
)

@Tag(
        "Spring"
)

通过:

getAnnotationsByType()

读取全部标签。


一百二十二、练习题 10:简易 IOC

组合:

@Component
@Autowired
反射
Map 容器

完成:

自动创建对象
自动注入字段

一百二十三、注解必须掌握 API

isAnnotationPresent()

getAnnotation()

getAnnotations()

getDeclaredAnnotations()

getAnnotationsByType()

一百二十四、注解必须掌握元注解

@Target

@Retention

@Documented

@Inherited

@Repeatable

一百二十五、必须掌握 Retention

SOURCE
只保留源码

CLASS
保留到字节码

RUNTIME
运行时可反射读取

一百二十六、必须掌握 Target

至少认识:

TYPE

FIELD

METHOD

PARAMETER

CONSTRUCTOR

一百二十七、必须回答的问题

学完后应该能回答:

1. 什么是注解?

2. 注解和注释有什么区别?

3. 注解本身会自动执行功能吗?

4. @Target 是干什么的?

5. @Retention 是干什么的?

6. SOURCE / CLASS / RUNTIME 有什么区别?

7. 什么是元注解?

8. 如何定义自定义注解?

9. 注解属性可以有哪些类型?

10. value 为什么可以省略属性名?

11. 如何通过反射读取注解?

12. @Inherited 有什么作用?

13. @Repeatable 有什么作用?

14. Spring 为什么大量使用注解?

15. 注解和反射是什么关系?

一百二十八、反射 + 内省 + 注解整体关系

现在可以把三篇内容完整串起来:

反射
│
├─ Class
├─ Constructor
├─ Field
└─ Method
    │
    ▼
提供运行时动态操作能力

内省:

Class
↓
Introspector
↓
BeanInfo
↓
PropertyDescriptor
↓
Getter / Setter

负责:

JavaBean 属性分析

注解:

@Target
@Retention
自定义 Annotation
↓
附加元数据
↓
反射读取
↓
框架执行逻辑

一百二十九、框架底层整体流程

例如 Spring:

扫描 classpath
↓
得到 Class
↓
反射读取 @Component / @Service
↓
反射创建对象
↓
读取 @Autowired
↓
字段或 Setter 注入
↓
Bean 保存 IOC 容器

一百三十、MyBatis 整体思路

读取 XML / 注解
↓
获得 SQL 和映射信息
↓
执行 JDBC
↓
获得 ResultSet
↓
反射 / 属性映射
↓
创建 JavaBean
↓
赋值

一百三十一、JUnit 整体思路

扫描测试类
↓
反射发现 @Test
↓
读取 @BeforeEach
↓
创建测试对象
↓
invoke 测试方法
↓
处理断言结果

一百三十二、本章知识结构

Java Annotation
│
├─ 内置注解
│   ├─ @Override
│   ├─ @Deprecated
│   ├─ @SuppressWarnings
│   └─ @FunctionalInterface
│
├─ 自定义注解
│   ├─ @interface
│   ├─ 属性
│   └─ default
│
├─ 元注解
│   ├─ @Target
│   ├─ @Retention
│   ├─ @Documented
│   ├─ @Inherited
│   └─ @Repeatable
│
├─ 反射读取
│   ├─ 类注解
│   ├─ 方法注解
│   ├─ 字段注解
│   └─ 参数注解
│
└─ 框架应用
    ├─ JUnit
    ├─ Spring
    ├─ Spring MVC
    ├─ MyBatis
    └─ AOP

一百三十三、本章总结

注解最核心的一句话:

注解是给程序元素附加元数据的一种机制。

注解自己通常:

不负责真正执行功能

真正负责功能的是:

编译器

反射

框架

注解处理器

最核心流程:

定义注解
↓
使用注解
↓
扫描程序元素
↓
读取注解
↓
根据注解执行逻辑

现在:

反射
内省
注解

已经完整学完。

这三个知识点共同构成了很多 Java 框架自动化能力的基础:

反射
提供动态操作

内省
提供 Bean 属性识别

注解
提供元数据和规则声明

后面学习:

MyBatis

Servlet

SpringBoot

AOP

事务

权限

时,你会不断看到这三个知识点的影子。

下一篇正式进入:

Lambda 表达式

开始学习 Java 8 以后非常重要的:

函数式编程基础