1. Spring元数据Metadata
元数据:描述数据的数据。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()));
}
}
运行结果如下
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中的元数据类继承体系
要注意的地方:
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"));
}
}
运行结果
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的实例
在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();
}
}
再次运行测试类,运行结果如下:
可以看出,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);
}
}
}
运行结果
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);
}
}
}
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);
}
}
}
运行结果:
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可以实现模块之间的解耦。
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);
});
}
}
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();
}
}
3. ConfigurationClassPostProcessor
4. 类的实例化过程
a. 父类静态代码块
b. 子类静态代码块
c. 父类构造代码块
d. 父类构造器
e. 子类构造代码块
f. 子类构造器
5. 源码分析