您现在的位置是:首页 >技术交流 >Spring底层架构核心概念网站首页技术交流

Spring底层架构核心概念

胡尚 2023-05-24 16:00:02
简介Spring底层架构核心概念

Spring底层架构核心概念

BeanDefinition

Bean定义,存在很多属性来描述一个Bean的特点。BeanDefinition是一个接口

  • beanClass:表示Bean的Class类型
  • scope:表示Bean作用域,单例或原型等等
  • lazyInit:是否为懒加载
  • initMethodName:初始化时要执行的方法
  • destroyMethodName:销毁时要执行的方法
  • … …

声明式定义Bean:@Bean @Component <Bean/>

编程式定义Bean:

public static void main(String[] args) {
    // 定义Spring容器
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);

    // 定义BeanDefinition
    AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
    beanDefinition.setBeanClass(UserService.class);
    beanDefinition.setScope("singleton");
    // 注册进容器中
    applicationContext.registerBeanDefinition("userService", beanDefinition);
}

误区:

  • 在java配置类中并不是加了@Bean注解的方法就立刻会执行,而是会先生成一个BeanDefinition对象,再之后遍历找出非懒加载的单例Bean再去执行方法。

BeanDefinition是一个接口

AbstractBeanDefinition抽象类实现了BeanDefinition接口

GenericBeanDefinition类继承了AbstractBeanDefinition抽象类,比较常见的类

RootBeanDefinition类继承了AbstractBeanDefinition抽象类,它跟合并BeanDefinition有关

ScannedGenericBeanDefinition类继承了GenericBeanDefinition类,ClassPathBeanDefinitionScanner的scan()方法创建的是该类

AnnotatedGenericBeanDefinition类继承了GenericBeanDefinition类,AnnotatedBeanDefinitionReader的register()方法创建的是该类



BeanDefinitionReader

BeanDefinition的读取器

我们有多种方式定义Bean,XML或者是注解,那么也就需要不同的类去读取解析这些内容并生成BeanDefinition,再存入Spring容器中,所以就定义了一个规范BeanDefinitionReader接口

该接口有三个实现

public abstract class AbstractBeanDefinitionReader implements BeanDefinitionReader
    
public class GroovyBeanDefinitionReader extends AbstractBeanDefinitionReader
    public class AnnotationConfigApplicationContext extends GenericApplicationContext

public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader


AnnotatedBeanDefinitionReader

能直接将某个类转换为BeanDefinition,并解析类上的注解

它能解析的注解是:@Conditional、@Scope、@Lazy、@Primary、@DependsOn、@Role、@Description

AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);

AnnotatedBeanDefinitionReader annotatedBeanDefinitionReader = new AnnotatedBeanDefinitionReader(applicationContext);
annotatedBeanDefinitionReader.registerBean(User.class);

System.out.println(applicationContext.getBean("user"));

我们也可以直接使用applicationContext.register()方法注册,它底层实际上就是调用的AnnotatedBeanDefinitionReader.register()方法



XmlBeanDefinitionReader

可以解析<bean/>标签

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(context);
int i = xmlBeanDefinitionReader.loadBeanDefinitions("spring.xml");

System.out.println(context.getBean("user"));


ClassPathBeanDefinitionScanner

扫描器,作用和BeanDefinitionReader类似,可以扫描某个包路径下的类。

比如扫描到的类上如果存在@Component这一类注解,那么就会把这个类解析为一个BeanDefinition

// 构造方法中没有指定配置类,也就没有包扫描路径
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.refresh();

ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context);
scanner.scan("com.hs");

System.out.println(context.getBean("userService"));

我们也可以直接使用applicationContext.scan()方法执行包扫描路径,它底层实际上就是调用的ClassPathBeanDefinitionScanner.scan()方法



BeanFactory

Bean工厂,复制创建Bean,并提供获取Bean的API方法。

而ApplicationContext也是BeanFactory的一种,他们都是接口,ApplicationContext继承了BeanFactory。但ApplicationContext还继承了很多其他的接口,它的功能更强大。比如MessageSource表示国际化,ApplicationEventPublisher表示事件发布,EnvironmentCapable表示获取环境变量,等等


当我们new一个ApplicationContext,其底层会new一个BeanFactory出来,当我们调用getBean()方法时实际上底层也是调用的BeanFactory的getBean()方法。

BeanFactory接口有一个很重要的实现类:DefaultListableBeanFactory

// 直接使用DefaultListableBeanFactory,而不是ApplicationContext的某个实现类
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();

// 创建一个beanDefinition
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
beanDefinition.setBeanClass(User.class);

// 注册进BeanFactory中
beanFactory.registerBeanDefinition("user", beanDefinition);

System.out.println(beanFactory.getBean("user"));

DefaultListableBeanFactory它实现了很多接口,增加了很多功能

在这里插入图片描述



ApplicationContext

它也是一个BeanFactory,它还继承了一些其他的接口 有了一些其他的功能:

  • EnvironmentCapable,可以获取运行时环境,但没有设置的功能
  • ListableBeanFactory,拥有获取beanNames的功能
  • HierarchicalBeanFactory,分层,拥有获取父BeanFactory的功能
  • MessageSource,国际化功能
  • ApplicationEventPublisher,拥有广播事件的功能,ApplicationContext没有添加事件监听器的功能
  • ResourcePatternResolver,资源加载器,可以一次性获取多个资源(文件资源等等)

其中ApplicationContext有两个重要的实现类:AnnotationConfigApplicationContext和ClassPathXmlApplicationContext

现在比较常用的是AnnotationConfigApplicationContext,这两个实现类就不过多分析了。



国际化

我们首先创建一个国际化文件

在这里插入图片描述

指定文件名和语言

在这里插入图片描述

然后就有了这样的三个文件,文件名为errorMessage,语言有三种 en/sc/tc

在这里插入图片描述


定义一个Bean

@Bean
public MessageSource messageSource() {
   ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
    // 指定文件名
   messageSource.setBasename("errorMessage");
   return messageSource;
}

有了这个Bean我们就可以在任意要进行国际化的地方使用该MessageSource,调用messageSource.getMessage()方法

@Autowired
private MessageSource messageSource;

public void test(){
    System.out.println(messageSource.getMessage("SYS001", null, new Locale("en")));

}

因为ApplicationContext也有国际化功能,我们也可以在类中实现ApplicationContextAware 得到ApplicationContext对象,再这样使用

System.out.println(applicationContext.getMessage("SYS001", null, new Locale("en")));


资源加载

很多东西资源,比如文件资源,网络资源,比如可以通过ApplicationContext获取某个文件的内容,或者是网络资源

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

Resource resource = context.getResource("file://D:\project\tuling\src\main\java\com\zhouyu\aspect\ZhouyuAspect.java");
System.out.println(resource.contentLength());
System.out.println(resource.getFilename());

Resource resource1 = context.getResource("https://www.baidu.com");
System.out.println(resource1.contentLength());
System.out.println(resource1.getURL());

Resource resource2 = context.getResource("classpath:spring.xml");
System.out.println(resource2.contentLength());
System.out.println(resource2.getURL());

也还可以一次性获取多个

Resource[] resources = context.getResources("classpath:com/zhouyu/*.class");
for (Resource resource : resources) {
	System.out.println(resource.contentLength());
	System.out.println(resource.getFilename());
}

在Spring底层源码中,就可以通过该功能获取xml配置文件中的内容,还有包扫描。



获取运行时环境

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

// 操作系统层面的环境变量
Map<String, Object> systemEnvironment = context.getEnvironment().getSystemEnvironment();
System.out.println(systemEnvironment);

System.out.println("=======");

// 操作系统层面的配置
Map<String, Object> systemProperties = context.getEnvironment().getSystemProperties();
System.out.println(systemProperties);

System.out.println("=======");

// 这个是比较全的,包含了上面两种以及properties配置文件中的内容
MutablePropertySources propertySources = context.getEnvironment().getPropertySources();
System.out.println(propertySources);

System.out.println("=======");

// 上面打印的内容其实都是获取的Environment对象中内容,我们也可以直接获取特定的配置
System.out.println(context.getEnvironment().getProperty("JAVA_HOME"));
System.out.println(context.getEnvironment().getProperty("sun.jnu.encoding"));
// 在配置类上面使用 @PropertySource("classpath:spring.properties") 加载某个properties文件
System.out.println(context.getEnvironment().getProperty("这里就可以获取properties文件中的配置项"));

输出结果如下图所示

在这里插入图片描述



事件发布

在Spring框架中,有三个关键类接口

分别是

  • 事件抽象类:ApplicationEvent
  • 事件发布者接口:ApplicationEventPublisher
  • 事件监听接口:ApplicationListenr

我们先定义一个事件,创建一个类,继承ApplicationEvent,然后重写构造方法

public class EmailEvent extends ApplicationEvent {

	public EmailEvent(Object source) {
		super(source);
	}

}

再定义一个事件监听器

@Bean
public ApplicationListener applicationListener() {
    return new ApplicationListener() {
        @Override
        public void onApplicationEvent(ApplicationEvent event) {
            
            if (event instanceof EmailEvent){
                // 自定义逻辑
                System.out.println("接收到了一个Email事件:" + event.getSource());
            } else {
                System.out.println("接收到了一个事件:" + event.getSource());
            }
            
        }
    };
}

然后发布一个事件

context.publishEvent("hushang");

除了使用ApplicationContext对象发布时间之外,在平时的工作中,比较常见的是使用依赖注入的方式发布事件

@Component
public class UserService {

	@Autowired
	private OrderService orderService;

	@Autowired
	private ApplicationEventPublisher applicationEventPublisher;

	public void test(){
		System.out.println("test()...");

		EmailEvent emailEvent = new EmailEvent("hs");
		applicationEventPublisher.publishEvent(emailEvent);
	}
}

此时会输出两遍,这是因为Spring在启动时就会发布一个事件。

接收到了一个事件:org.springframework.context.annotation.AnnotationConfigApplicationContext@7a8119...
test()...
接收到了一个Email事件:hs


类型转换

在Spring的源码中,关于类型转换的场景会很常见,比如就很有可能需要把String转换为其他类型。

比如我现在有一个User类

public class User {

   private String name;

   public String getName() {
      return name;
   }

   public void setName(String name) {
      this.name = name;
   }
}

我现在先把String转换为User

@Component
public class UserService {

    // 这里能够直接赋值到User对象中的那么name属性中
   @Value("hushang")
   private User user;


   public void test(){
      System.out.println("test()...");
   }
}

这是会报错的,因为不能把String转换为User,我们需要进行类型转换相关的操作



PropertyEditor

这其实是JDK中提供的类型转化工具类

创建一个类

public class StringToUserPropertyEditor extends PropertyEditorSupport implements PropertyEditor {

   @Override
   public void setAsText(String text) throws IllegalArgumentException {
      User user = new User();
      user.setName(text);
      this.setValue(user);
   }
}

基本使用

StringToUserPropertyEditor propertyEditor = new StringToUserPropertyEditor();
propertyEditor.setAsText("1");
User value = (User) propertyEditor.getValue();
System.out.println(value);

向Spring中注册PropertyEditor

@Bean
public CustomEditorConfigurer customEditorConfigurer() {
   CustomEditorConfigurer customEditorConfigurer = new CustomEditorConfigurer();
    
   Map<Class<?>, Class<? extends PropertyEditor>> propertyEditorMap = new HashMap<>();
   // 表示StringToUserPropertyEditor可以将String转化成User类型
   // 在Spring源码中,如果发现当前对象是String,而需要的类型是User,就会使用该PropertyEditor来做类型转化
   propertyEditorMap.put(User.class, StringToUserPropertyEditor.class);
    
   customEditorConfigurer.setCustomEditors(propertyEditorMap);
   return customEditorConfigurer;
}

现在就能正常注入了

@Value("hushang")
private User user;


ConversionService

Spring中提供的类型转化服务,它比PropertyEditor更强大

创建一个类

public class StringToUserConverter implements ConditionalGenericConverter {

	@Override
	public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
        // 判断两个类型是否为String和User
		return sourceType.getType().equals(String.class) && targetType.getType().equals(User.class);
	}

	@Override
	public Set<ConvertiblePair> getConvertibleTypes() {
        // 可转换的类型
		return Collections.singleton(new ConvertiblePair(String.class, User.class));
	}

	@Override
	public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
        // 自定义转换逻辑
		User user = new User();
		user.setName((String)source);
		return user;
	}
}

基本使用

DefaultConversionService conversionService = new DefaultConversionService();
conversionService.addConverter(new StringToUserConverter());
User value = conversionService.convert("1", User.class);
System.out.println(value);

向Spring中注册PropertyEditor

@Bean
public ConversionServiceFactoryBean conversionService() {
    ConversionServiceFactoryBean conversionServiceFactoryBean = new ConversionServiceFactoryBean();
    conversionServiceFactoryBean.setConverters(Collections.singleton(new StringToUserConverter()));

    return conversionServiceFactoryBean;
}


TypeConverter

整合了PropertyEditor和ConversionService的功能,Spring底层源码中使用的是TypeConverter,因为它前两种转换都支持,Spring也不确定我们程序员会使用前两种的哪一种类型转换器,所以Spring就直接使用TypeConverter,将我们定义的类型转换器收集起来,然后直接调用convertIfNecessary()方法转换即可


整合PropertyEditor方式

public static void main(String[] args) {
    SimpleTypeConverter typeConverter = new SimpleTypeConverter();

    // 自定义的StringToUserPropertyEditor类
    typeConverter.registerCustomEditor(User.class, new StringToUserPropertyEditor());

    User value = typeConverter.convertIfNecessary("1", User.class);
    System.out.println(value);
}

整合了ConversionService方式

public static void main(String[] args) {
    SimpleTypeConverter typeConverter = new SimpleTypeConverter();

    // 自定义的StringToUserConverter类
    DefaultConversionService conversionService = new DefaultConversionService();
    conversionService.addConverter(new StringToUserConverter());
    typeConverter.setConversionService(conversionService);

    User value = typeConverter.convertIfNecessary("1", User.class);
    System.out.println(value);
}


OrderComparator

OrderComparator是Spring所提供的一种比较器,可以用来根据@Order注解或实现Ordered接口来执行值进行比较,从而可以进行排序。


实现Ordered接口的方式

// 先定义两个类 都实现Ordered接口,在getOrder()方法中指定要进行排序的数值
public class A implements Ordered {

	@Override
	public int getOrder() {
		return 3;
	}

	@Override
	public String toString() {
		return this.getClass().getSimpleName();
	}
}



public class B implements Ordered {

	@Override
	public int getOrder() {
		return 2;
	}

	@Override
	public String toString() {
		return this.getClass().getSimpleName();
	}
}

接下来进行排序比较

public class Main {

	public static void main(String[] args) {
		A a = new A(); // order=3
		B b = new B(); // order=2

		OrderComparator comparator = new OrderComparator();
		System.out.println(comparator.compare(a, b));  // 1

		List list = new ArrayList<>();
		list.add(a);
		list.add(b);

		// 按order值升序排序
		list.sort(comparator);

		System.out.println(list);  // B,A
	}
}

根据@Order注解的方式

Spring中还提供了一个OrderComparator的子类:AnnotationAwareOrderComparator,它支持用@Order来指定order值。比如:

@Order(3)
public class A {

	@Override
	public String toString() {
		return this.getClass().getSimpleName();
	}

}




@Order(2)
public class B {

	@Override
	public String toString() {
		return this.getClass().getSimpleName();
	}

}

进行排序比较

public class Main {

	public static void main(String[] args) {
		A a = new A(); // order=3
		B b = new B(); // order=2

		AnnotationAwareOrderComparator comparator = new AnnotationAwareOrderComparator();
		System.out.println(comparator.compare(a, b)); // 1

		List list = new ArrayList<>();
		list.add(a);
		list.add(b);

		// 按order值升序排序
		list.sort(comparator);

		System.out.println(list); // B,A
	}
}


BeanPostProcess

Bean的后置处理器,我们可以定义多个BeanPostProcessor,在创建Bean时,每个Bean都会执行这其中的前置和后置方法,我们也可以加if来判断给特定某个Bean执行某些特定的方法。

我们可以自定义一个类,实现BeanPostProcessor接口,重写接口的抽象方法,然后把该类注册进Spring容器中


我们可以通过定义BeanPostProcessor来干涉Spring创建Bean的过程。

Spring框架的扩展性主要就是体现在PostProcessor中。



BeanFactoryPostProcessor

Bean工厂的后置处理器,和BeanPostProcessor类似,只不过他们针对的对象不一样,一个是Bean,一个的BeanFactory。

BeanFactoryPostProcessor是干涉BeanFactory的创建过程。比如,我们可以这样定义一个BeanFactoryPostProcessor:

@Component
public class MyFactoryPostProcessor implements BeanFactoryPostProcessor {

	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
		System.out.println("加工beanFactory");
	}
}

典型的引用就是从配置文件中读取出来的${jdbc.username}配置项,刚开始生成BeanDefinition时值还是这个字符串,经过BeanFactoryPostProcessor之后才会替换为具体的配置项



FactoryBean

Spring中一个Bean的创建过程有很多的步骤,如果我们想要一个Bean完全由我们自己来创建就可以使用FactoryBean机制。

// 刚开始下面会创建一个BeanName为myFactoryBean的bean对象,对应的value是MyFactoryBean类型的对象
// 当getBean()方法调用的时候就会判断是否实现了FactoryBean接口,然后返回的是getObject()方法返回的Bean对象
@Component
public class MyFactoryBean implements FactoryBean {

	@Override
	public Object getObject() throws Exception {
		UserService userService = new UserService();

		return userService;
	}

	@Override
	public Class<?> getObjectType() {
		return UserService.class;
	}
}

我们自己创建了一个UserService的Bean对象,这种方式创建的bean对象只会经过初始化后的步骤,依赖注入和初始化哪些就不会有了。

而普通@Bean注解方式创建的Bean对象就会走完成的创建流程。虽然这两种方式都是在方法中直接new一个对象并返回。



ExcludeFilter和IncludeFilter

Spring在包扫描过程中使用的,ExcludeFilter表示排除过滤器,IncludeFilter表示包含过滤器。


案例

扫描com.hs包下面所有类,但是排除UserService类,也就是就算它上面有@Component注解也不会成为Bean。

@ComponentScan(value = "com.hs",
		excludeFilters = {@ComponentScan.Filter(
            	type = FilterType.ASSIGNABLE_TYPE, 
            	classes = UserService.class)})
public class AppConfig {
}

扫描com.hs包下面所有类,UserService类就算它上面没有@Component注解也会成为Bean。

@ComponentScan(value = "com.zhouyu",
		includeFilters = {@ComponentScan.Filter(
            	type = FilterType.ASSIGNABLE_TYPE, 
            	classes = UserService.class)})
public class AppConfig {
}

FilterType分为:

  1. ANNOTATION:表示是否包含某个注解
  2. ASSIGNABLE_TYPE:表示是否是某个类
  3. ASPECTJ:表示否是符合某个Aspectj表达式
  4. REGEX:表示是否符合某个正则表达式
  5. CUSTOM:自定义

在Spring的扫描逻辑中,默认会添加一个AnnotationTypeFilterincludeFilters,表示默认情况下Spring扫描过程中会认为类上有@Component注解的就是Bean。



MetadataReader、ClassMetadata、AnnotationMetadata

Spring中需要去解析类的元数据信息,比如类名、方法名、类上注解等。所以Spring对类的元数据做了抽象并提供了一些工具类

MetadataReader表示类的元数据读取器,默认实现类为SimpleMetadataReader。比如:

public class Test {

	public static void main(String[] args) throws IOException {
		SimpleMetadataReaderFactory simpleMetadataReaderFactory = new SimpleMetadataReaderFactory();
		
        // 构造一个MetadataReader
        MetadataReader metadataReader = simpleMetadataReaderFactory.getMetadataReader("com.hs.service.UserService");
		
        // 得到一个ClassMetadata,并获取了类名
        ClassMetadata classMetadata = metadataReader.getClassMetadata();
        System.out.println(classMetadata.getClassName());
        
        // 获取一个AnnotationMetadata,并获取类上的注解信息
        AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
        // 判断类上面是否存在@Component注解
        annotationMetadata.hasMetaAnnotation(Component.class.getName())
		for (String annotationType : annotationMetadata.getAnnotationTypes()) {
			System.out.println(annotationType);
		}
	}
}

SimpleMetadataReader去解析类时,使用的ASM技术。


为什么要使用ASM技术?

我们可以把一个字节码先通过类加载器加载之后,得到Class对象,再去解析,但这种方式不太好。

Spring启动的时候需要去扫描,如果指定的包路径比较宽泛,那么扫描的类是非常多的,那如果在Spring启动时就把这些类全部加载进JVM了,这样不太好,因为JVM的类加载是在类要使用时才会加载,而现在都没有使用就都加载了,所以使用了ASM技术。

ASM技术就不需要进行类加载,直接去解析字节码文件

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。