1. Spring元数据Metadata

元数据:描述数据的数据。Spring中元数据相关的类,其类结构图大致如下:

5. SpringBoot高级篇_spring

1.1 ClassMetadata

Class代表一个类,ClassMetadata就是一个类的元数据,也就是说,ClassMetadata是用来描述一个类的方方面面的信息的。

有一个Person类,如下,注意在Person类上添加@ComponentScan注解仅仅是为了测试,并不是为了真正地去扫描什么包的。

@Component

@ComponentScan(basePackages = {"com.gao.a", "com.gao.b"})

public class Person {

private Integer id;

private String name;

public Integer getId() {

return id;

}

public void setId(Integer id) {

this.id = id;

}

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

public void run() {

我跑");

}

public void eat() {

我吃");

}

}

下面根据Preson.class来创建ClassMetadata对象,该ClassMetadata对象就是用来描述Person类的

public class AppTest {

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

ClassMetadata classMetadata = new StandardClassMetadata(Person.class);

类名:\t" + classMetadata.getClassName());

是否是接口:\t" + classMetadata.isInterface());

是否是注解:\t" + classMetadata.isAnnotation());

是否是抽象类:\t" + classMetadata.isAbstract());

能否实例化:\t" + classMetadata.isConcrete());

是否为final:\t" + classMetadata.isFinal());

是否有内部类:\t" + classMetadata.hasEnclosingClass());

是否有父类:\t" + classMetadata.hasSuperClass());

获取父类类名:\t" + classMetadata.getSuperClassName());

实现的接口:\t" + Arrays.toString(classMetadata.getInterfaceNames()));

}

}

运行结果如下

5. SpringBoot高级篇_System_02

1.2 AnnotatedTypeMetadata

AnnotatedType代表“注解元素”,所谓注解元素,就是能够在其上添加注解的东西。比如我们能够在Class、Method、Constructor、Parameter和Field上添加注解,那么它们都可以称之为注解元素。那么AnnotatedTypeMetadata就是用来描述一个注解元素的方方面面的。

public class AppTest {

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

AnnotatedTypeMetadata atmd = new StandardAnnotationMetadata(Person.class);

System.out.println("@Component:\t" + atmd.isAnnotated("org.springframework.stereotype.Component"));

System.out.println("@Service:\t" + atmd.isAnnotated("org.springframework.stereotype.Service"));

获取Component注解的所有属性========");

Map<String, Object> map = atmd.getAnnotationAttributes("org.springframework.stereotype.Component");

map.forEach((k, v) -> System.out.println(k + ": " + v));

获取ComponentScan注解的所有属性========");

Map<String, Object> map2 = atmd.getAnnotationAttributes("org.springframework.context.annotation.ComponentScan");

map2.forEach((k, v) -> {

if (v instanceof String[]) {

System.out.println(k + ": " + Arrays.toString((String[]) v));

} else {

System.out.println(k + ": " + v);

}

});

}

}

我们再次观察Spring中的元数据类继承体系

5. SpringBoot高级篇_元数据_03

要注意的地方:

1. AnnotationMetadata接口就是对AnnotatedTypeMetadata和ClassMetadata这两个接口方法的汇总,同时也添加了更多的描述注解元素的方法。

2. StandardAnnotationMetadata实现了AnnotationMetadata接口,也就是间接地实现了这两个接口:AnnotatedTypeMetadata和ClassMetadata,这也就意味着StandardAnnotationMetadata要实现这两个接口中的抽象方法和AnnotationMetadata接口中独有的抽象方法,而StandardAnnotationMetadata又继承了StandardClassMetadata类,所以StandardAnnotationMetadata借StandardClassMetadata类实现了ClassMetadata接口中的抽象方法,它自己就只需实现AnnotatedTypeMetadata接口中的抽象方法和AnnotationMetadata中的独有的抽象方法。

1.3 AnnotationMetadata

AnnotationMetadata是一个接口,它本身并不是一个注解,不要被它的名字迷惑了。且它同时继承了ClassMetadata和AnnotatedTypeMetadata这两个接口,所以AnnotationMetadata接口可以同时将一个类当做普通Class来描述(获取类的方方面面的信息),也可以当做一个注解元素来描述(获取类上注解的方方面面的信息)。也就是说,AnnotationMetadata具备了这两个接口的所有方法。

public class AppTest {

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

AnnotationMetadata amd = new StandardAnnotationMetadata(Person.class);

的方法

是否是注解:\t" + amd.isAnnotation());

的方法

System.out.println("@Component:\t" + amd.isAnnotated("org.springframework.stereotype.Component"));

}

}

AnnotationMetadata还有自己独有的方法,如下:

public class AppTest {

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

AnnotationMetadata amd = new StandardAnnotationMetadata(Person.class);

获取Person.class类上所有注解的全类名==============");

Set<String> annotationTypes = amd.getAnnotationTypes();

for (String annotationType : annotationTypes) {

System.out.println(annotationType);

}

获取Person.class类上指定注解的元注解全类名==============");

Set<String> metaAnnotationTypes = amd.getMetaAnnotationTypes("org.springframework.stereotype.Component");

for (String metaAnnotationType : metaAnnotationTypes) {

System.out.println(metaAnnotationType);

}

判断Person.class类上是否包含指定注解==============");

System.out.println(amd.hasAnnotation("org.springframework.stereotype.Component"));

System.out.println(amd.hasAnnotation("org.springframework.stereotype.Indexed"));

判断Person.class类上的注解是否包含指定注解==============");

System.out.println(amd.hasMetaAnnotation("org.springframework.stereotype.Component"));

System.out.println(amd.hasMetaAnnotation("org.springframework.stereotype.Indexed"));

}

}

运行结果

5. SpringBoot高级篇_元数据_04

1.4 MetadataReader

以上的例子,都是根据Person.class来获取Metadata的。这就需要JVM先加载Person.class,再获取Person的Metadata。而MetadataReader接口的作用,就是可以直接通过String来获取Metadata。从而不需要加载Person.class。

MetadataReader是一个接口,它默认的实现类SimpleMetadataReader并不是public class,而是包访问权限。所以我们只能通过MetadataReaderFactory来获取MetadataReader的实例了。MetadataReaderFactory也是一个接口,我们使用它的实现类SimpleMetadataReaderFactory,如下:

MetadataReaderFactory metadataReaderFactory = new SimpleMetadataReaderFactory();

MetadataReader mr = metadataReaderFactory.getMetadataReader("com.gao.test.Person");

AnnotationMetadata amd = mr.getAnnotationMetadata();

SimpleMetadataReaderFactory又有一个子类:CachingMetadataReaderFactory,该类没有什么太特殊的地方,仅仅是提供了缓存能力,比如我们多次获取Person类的MetadataReader时,只有第一次会真正地解析Person的字节码,以后第二次、第三次都是直接使用第一次解析好的结果。

MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory();

MetadataReader mr = metadataReaderFactory.getMetadataReader("com.gao.test.Person");

AnnotationMetadata amd = mr.getAnnotationMetadata();

1.5 搞懂AnnotationMetadata的价值

从Spring 3.0开始,就大量使用注解编程了,我们在Spring的源码中经常看到AnnotationMetadata的身影,每当AnnotationMetadata出现的时候,就告诉自己,通过这个AnnotationMetadata接口,我们可以获取目标类上的所有注解,包括注解上的元注解,也能获取到这些注解的属性值。

2. @import注解

@import注解的作用

a. 声明一个bean

b. 导入被@Configuration所注解的配置类

c. 导入ImportSelector的实现类

d. 导入ImportBeanDefinitionRegister的实现类

2.1 声明一个bean

Dog类

public class Dog {

}

Cat类

public class Cat {

}

Panda类

public class Panda {

}

RootConfig类,注意该配置类并没有管理Panda类的实例

@Configuration

public class RootConfig {

@Bean

public Dog dog() {

return new Dog();

}

@Bean

public Cat cat() {

return new Cat();

}

}

AppTest类

public class AppTest {

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

AnnotationConfigApplicationContext acac = //

new AnnotationConfigApplicationContext(RootConfig.class);

String[] beanDefinitionNames = acac.getBeanDefinitionNames();

for (String beanDefinitionName : beanDefinitionNames) {

System.out.println(beanDefinitionName);

}

}

}

运行结果,注意ioc容器并没有管理Panda的实例

5. SpringBoot高级篇_spring_05

在RootConfig配置上再添加一个@Import({Panda.class})注解,如下:

@Configuration

@Import({Panda.class})

public class RootConfig {

@Bean

public Dog dog() {

return new Dog();

}

@Bean

public Cat cat() {

return new Cat();

}

}

再次运行测试类,运行结果如下:

5. SpringBoot高级篇_System_06

可以看出,ioc容器管理了Panda类的实例

2.2 导入被@Configuration所注解的配置类

在上面的例子中,再添加一个MyConfig配置类

@Configuration

public class MyConfig {

@Bean

public Panda panda() {

return new Panda();

}

}

然后修改RootConfig类,将原来的 @Import(Panda.class) 替换为 @Import(MyConfig.class)

@Configuration

@Import(MyConfig.class)

public class RootConfig {

@Bean

public Dog dog() {

return new Dog();

}

@Bean

public Cat cat() {

return new Cat();

}

}

这样的情况下,ioc容器也会将MyConfig配置类中所创建的bean全部“吸纳”进来,运行测试类:

public class AppTest {

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

AnnotationConfigApplicationContext acac = //

new AnnotationConfigApplicationContext(RootConfig.class);

String[] beanDefinitionNames = acac.getBeanDefinitionNames();

for (String beanDefinitionName : beanDefinitionNames) {

System.out.println(beanDefinitionName);

}

}

}

运行结果

5. SpringBoot高级篇_元数据_07

2.3 导入ImportSelector的实现类

在上面的例子中,添加一个ImportSelector的实现类:

public class MyImportSelector implements ImportSelector {

@Override

public String[] selectImports(AnnotationMetadata importingClassMetadata) {

return new String[]{"com.gao.test.Panda"};

}

}

然后修改RootConfig类,将原来的 @Import(MyConfig.class) 替换为@Import(MyImportSelector.class)

@Configuration

@Import(MyImportSelector.class)

public class RootConfig {

@Bean

public Dog dog() {

return new Dog();

}

@Bean

public Cat cat() {

return new Cat();

}

}

运行测试类

public class AppTest {

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

AnnotationConfigApplicationContext acac = //

new AnnotationConfigApplicationContext(RootConfig.class);

String[] beanDefinitionNames = acac.getBeanDefinitionNames();

for (String beanDefinitionName : beanDefinitionNames) {

System.out.println(beanDefinitionName);

}

}

}

5. SpringBoot高级篇_spring_08

2.4 导入ImportBeanDefinitionRegister的实现类

在上面的例子中,再添加一个类:

public class MyImportBeanDefinitionRegister implements ImportBeanDefinitionRegistrar {

@Override

public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

RootBeanDefinition beanDefinition = new RootBeanDefinition();

beanDefinition.setBeanClass(Panda.class);

registry.registerBeanDefinition("panda...", beanDefinition);

}

}

注意,上面的registerBeanDefinitions方法的第一个参数importingClassMetadata代表的就是@Import注解所注解的类的元数据,也就是说通过这个importingClassMetadata参数,可以获取到@Import所注解的类所有注解信息,尽管这个功能在当前例子中并不会使用到,但是这里还是要说明一下。

修改RootConfig,@Import(MyImportSelector.class)换为@Import(MyImportBeanDefinitionRegister.class)

@Configuration

@Import(MyImportBeanDefinitionRegister.class)

public class RootConfig {

@Bean

public Dog dog() {

return new Dog();

}

@Bean

public Cat cat() {

return new Cat();

}

}

执行测试类

public class AppTest {

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

AnnotationConfigApplicationContext acac = //

new AnnotationConfigApplicationContext(RootConfig.class);

String[] beanDefinitionNames = acac.getBeanDefinitionNames();

for (String beanDefinitionName : beanDefinitionNames) {

System.out.println(beanDefinitionName);

}

}

}

运行结果:

5. SpringBoot高级篇_spring_09

3. SPI

3.1 SPI是什么?

a. SPI的全名为Service Provider Interface

b. SPI是JDK自带的功能,不需要任何第三方类库

3.2 SPI能做什么?

a. 利用ServiceLoader自动发现接口的实现类。

b. 为框架提供扩展点,一般用于服务器端程序员。

3.3 SPI简单使用示例:

a. 创建IUserDao接口,方法自拟,比如:save和delete(参数有没有无所谓,关注点不在那里)

b. 创建IUserDao接口的实现类: UserDaoImpl和UserDaoImpl2

c. 以Maven项目为例,在resources文件夹下,再创建一个文件夹:META-INF/services文件夹。

d. 在META-INF/services文件夹下创建文件,名为:com.gao.dao.IUserDao(也就是IUserDao接口的全限定名)

e. 在名为“com.gao.dao.IUserDao”的文件中,写出接口的实现类的全限定名,可以写多个,如下:

com.gao.dao.impl.UserDaoImpl

com.gao.dao.impl.UserDaoImpl2

f. 在测试类中,使用ServiceLoader来获取指定接口的实现类的对象,并调用每一个对象的save方法,如下:

public class AppTest {

public static void main(String[] args) {

ServiceLoader<IUserDao> daos = ServiceLoader.load(IUserDao.class);

for (IUserDao dao : daos) {

dao.save();

}

}

}

g. 这个例子仅仅演示了SPI的基本流程,本没有发挥出SPI的真正价值

3.4 SPI常规使用

a. 创建spi-a项目,在其中定义IUserDao接口,但是并不提供实现类

b. 创建spi-b项目,在其中定义IUserDao接口,同时提供实现类,并且在spi-b的resources下创建“META-INF/services”目录,在其中创建以IUserDao接口全限定名为名字的文件,在该文件中写出spi-b中接口实现类的全限定名

c. spi-a引入spi-b

d.在spi-a中,使用ServiceLoader.load(IUserDao.class);就可以获取到spi-b中的实现类。

3.5 SPI意义何在?

a. ServiceLoader可以跨jar包访问到接口的所有实现类,这意味着,SPI可以实现模块之间的解耦。

5. SpringBoot高级篇_spring_10

b. SpringBoot就使用到了spi,只是配置文件的名字变了,但是原理是一样的。

4. 一些铺垫:

1. MultiValueMap

public class AppTest {

@Test

public void test() {

MultiValueMap<String, String> map = new LinkedMultiValueMap();

书包");

钱包");

豆包");

关公");

周公");

包公");

map.forEach((k, v) -> {

System.out.println(k + ": " + v);

});

}

}

5. SpringBoot高级篇_spring_11

Map的getOrDefault方法的使用:

TBD

2. 通过new RuntimeException().getStackTrace()获取堆栈轨迹

public class AppTest {

public static void f1() {

f2();

}

public static void f2() {

f3();

}

public static void f3() {

StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();

for (StackTraceElement stackTraceElement : stackTrace) {

System.out.println(stackTraceElement.getMethodName());

}

}

public static void main(String[] args) {

f1();

}

}

5. SpringBoot高级篇_元数据_12

3. ConfigurationClassPostProcessor

5. SpringBoot高级篇_元数据_13

4. 类的实例化过程

a. 父类静态代码块

b. 子类静态代码块

c. 父类构造代码块

d. 父类构造器

e. 子类构造代码块

f. 子类构造器

5. 源码分析