hexon
发布于 2025-12-02 / 7 阅读
0

3、Spring源码中核心组件的使用与解析

按照常理,上节课我们通过手写模拟了Spring的核心原理之后,已经具备了分析源码的基础。不过,在正式进入源码之前,我们还需要专门用一节课来熟悉Spring中的一些核心组件——它们就像一个个关键“点”。而像配置类解析这样的源码流程,则是一条“线”。只有把这些“点”理解透彻,才能连“线”成“面”,真正把握Spring的全貌。这些组件在Spring实际源码中确实都有应用,但本节课的重点不在于追踪它们具体如何被使用,而是先掌握每个组件本身的作用与使用方法。

主要内容:

  1. 核心组件BeanDefinition使用与底层解析

  2. 核心组件BeanDefinitionReader使用与底层解析

  3. 核心组件AnnotatedBeanDefinitionReader使用与底层解析

  4. 核心组件XmlBeanDefinitionReader使用与底层解析

  5. 核心组件ClassPathBeanDefinitionScanner使用与底层解析

  6. BeanDefinition合并的使用与底层解析

  7. 核心组件BeanFactory使用与底层解析

  8. 核心组件DefaultListableBeanFactory使用与底层解析

  9. 核心组件ApplicationContext使用与底层解析

  10. ApplicationContext国际化功能的使用与底层解析

  11. ApplicationContext资源加载功能的使用与底层解析

  12. ApplicationContext获取运行时环境功能的使用与底层解析

  13. ApplicationContext事件发布功能的使用与底层解析

  14. 类型转化之PropertyEditor的使用与底层解析

  15. 类型转化之ConversionService的使用与底层解析

  16. 类型转化之TypeConverter的使用与底层解析

  17. Order比较器OrderComparator的使用与底层解析

  18. Order比较器AnnotationAwareOrderComparator的使用与底层解析

BeanDefinition

BeanDefinition是非常非常核心的一个概念,一个BeanDefinition表示一个Bean定义,Spring会根据BeanDefinition来创建具体的Bean对象。

BeanDefinition中有些常用的属性:

  • beanClass:

  • scope:

  • lazyInit

  • initMethodName

  • destroyMethodName

  • ...


当我们使用<bean/>、@Bean、@Component等方式定义Bean时,Spring底层就会解析这些标签和注解生成对应的BeanDefinition对象。

上面这些方式可以理解成声明式的定义Bean

我们也可以通过代码的方式(编程式)直接定义和注册BeanDefinition,比如:

public class Main {
	public static void main(String[] args) {
		AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
		BeanDefinition beanDefinition = new AnnotatedGenericBeanDefinition(UserService.class);
		beanDefinition.setScope(BeanDefinition.SCOPE_PROTOTYPE);
		beanDefinition.setInitMethodName("test"); // 需要在UserService中定义一个test()方法
		applicationContext.registerBeanDefinition("userService", beanDefinition);

		System.out.println(applicationContext.getBean(UserService.class));
		System.out.println(applicationContext.getBean(UserService.class));
		System.out.println(applicationContext.getBean(UserService.class));
	}
}

BeanDefinitionReader

BeanDefinitionReader在我们使用Spring时用得少,但在Spring源码中用得多,相当于Spring源码的基础设施,顾名思义,BeanDefinitionReader是用来解析读取BeanDefinition对象的,主要有两个,一个是BeanDefinitionReader 用于从外部资源(如XML文件)读取配置,一个是 AnnotatedBeanDefinitionReader 用于在程序内部直接注册配置类

值得一提的是,BeanDefinitionReader是接口主要的实现类是XmlBeanDefinitionReader,而AnnotatedBeanDefinitionReader是单独的类,它没有实现BeanDefinitionReader接口

AnnotatedBeanDefinitionReader

作用是解析某个类上的注解信息从而得到BeanDefinition对象。

public class Main {
	public static void main(String[] args) {
		AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
        // 读取的BeanDefinition可能要放入容器,因此构造器要传入容器,只不过容器实现了BeanDefinitionRegistry接口
		AnnotatedBeanDefinitionReader annotatedBeanDefinitionReader = new AnnotatedBeanDefinitionReader(applicationContext);
        // 相当于去解析传入的类,生成BeanDefinition
		annotatedBeanDefinitionReader.register(UserService.class);

		System.out.println(applicationContext.getBean(UserService.class));
	}
}

但注意,此时UserService不需要加@Component注解,@Component注解和后面要讲的包扫描才有关系。(调试register方法)

会解析类上的:@Conditional、@Scope、@Primary、@Lazy、@Fallback(Spring6新增注解)、@DependsOn、@Role、@Description

其实这里还可以想到容器对象本身也应该可以注册,AnnotationConfigApplicationContext确实也有一个register方法,它内部就是用的AnnotatedBeanDefinitionReader的注册,就是一个委派。

上面的代码还有个趣的是传入的MyConfig其实最后也会默认被BeanDefinitionReader注册。所以MyConfig也是一个bean。可以验证一下:

// 这三行就相当于 AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.register(MyConfig.class);
applicationContext.refresh();

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

这个地方看AnnotationConfigApplicationContext的构造器就明白了

XmlBeanDefinitionReader

AnnotatedBeanDefinitionReader是用来解析类的,XmlBeanDefinitionReader是用来解析xml文件的。

public class Main {
	public static void main(String[] args) {
		AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
        // 与AnnotatedBeanDefinitionReader同理,也要传入一个容器
		XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(applicationContext);
		// 解析XML文件
        beanDefinitionReader.loadBeanDefinitions("spring.xml");

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

	}
}

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	   xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">


	<bean class="com.hexon.service.UserService" name="userService"></bean>

</beans>

ClassPathBeanDefinitionScanner

ClassPathBeanDefinitionScanner是扫描器,@ComponentScan注解的底层实现就是用的它,它作用是能对某个包路径进行扫描,从而得到BeanDefinition对象,默认情况下,它只能把加了@Component注解的类生成对应的BeanDefinition对象。

public class Main {
	public static void main(String[] args) {
		AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();

		ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(applicationContext);
		scanner.scan("com.hexon");

		applicationContext.refresh();  // 容器必须refresh才能使用

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

	}
}

此时UserService必须加上@Component注解。

为什么也可以在scan方法之前调用refresh?

Spring 容器必须调用 refresh() 方法完成初始化后才能正常使用,这是获取 Bean 的前提条件。

虽然技术上可以将 refresh() 放在 scan() 之前调用,但最佳实践仍然是将其置于后面。这是因为:

  • scan() 方法的作用是扫描指定包路径,将符合条件的组件解析为 BeanDefinition 注册到容器中

  • 如果先调用 refresh()(且未传入配置类),容器基于当前已注册的 BeanDefinition 初始化 Bean

  • 后续通过 scan() 扫描注册的 BeanDefinition 不会立即创建 Bean 实例,只有在首次调用 getBean() 时才会触发实例化,这实际上变成了一种延迟加载的行为

因此,为了确保所有 Bean 都能在容器启动时统一初始化,推荐遵循“先扫描注册,后刷新容器”的标准流程。

容器本身也可以scan,委派方式调用scanner实现的。

public static void main(String[] args) {
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
    applicationContext.scan("com.hexon");
    applicationContext.refresh();
    System.out.println(applicationContext.getBean("userService"));
}

甚至可以直接传包路径:

AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext("com.hexon");
// applicationContext.refresh(); // 注意不能重复refresh
System.out.println(applicationContext.getBean("userService"));

我们也可以自定义注解进行扫描:

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

此时我们的业务类UserService也换成使用我们自己定义的@MyComponent注解:

@MyComponent
public class UserService {

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

}

下面执行测试:

public class Main {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        applicationContext.scan("com.hexon");
        applicationContext.refresh();
        System.out.println(applicationContext.getBean("userService"));
    }
}

此时会报错,因为UserService只加了@MyComponent注解,Spring无法感知。有一种方法处理就是在@MyComponent注解上加上@Component注解,就能解决问题,这个是Spring默认的处理逻辑所支持的,它会判断注解上的注解。

另外一种做法,就是利用扫描器的机制,添加一个Filter:

public class Main {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
​
        // 注意这里必须使用ClassPathBeanDefinitionScanner,容器是没有addIncludeFilter方法的
        ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(applicationContext);
        scanner.addIncludeFilter(new TypeFilter() {
            @Override
            public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
                // MetadataReader类的元数据读取器,暂时先理解成一个类定义信息的抽象
                return metadataReader.getAnnotationMetadata().hasAnnotation(MyComponent.class.getName());
            }
        });
        scanner.scan("com.hexon");
​
        applicationContext.refresh();
        System.out.println(applicationContext.getBean("userService"));
    }
}

此时@MyComponent注解的定义:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
// 没有@Component也能工作
public @interface MyComponent {
}

此时再定义一个OrderService类:

@Component
public class OrderService {
}

容器启动后获取orderService这个名称的Bean,也一样可以,说明并没有影响原本的@Component的工作机制。因为@Component是在默认的Filter里面处理的,这个只要看new ClassPathBeanDefinitionScanner(applicationContext)这个构造器,跟下源码就能看出来includeFilters是一个集合,可以有多个。

BeanDefinition合并

在后续看源码的过程中,Spring在利用BeanDefinition创建Bean对象时,会判断一个BeanDefinition是否存在父BeanDefinition,如果存在则先进行合并,就像父子类一样,子类会继承或覆盖父类的属性,子BeanDefinition会继承或覆盖父BeanDefinition的属性。

public class Main {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
​
        AnnotatedGenericBeanDefinition parentBeanDefinition = new AnnotatedGenericBeanDefinition(UserService.class);
        parentBeanDefinition.setScope("prototype");
        context.registerBeanDefinition("parentUserService", parentBeanDefinition);
​
        // 下面的这个Definition未定义多例,但是它继承了父的,所以也是多例
        AnnotatedGenericBeanDefinition childBeanDefinition = new AnnotatedGenericBeanDefinition(UserService.class);
        childBeanDefinition.setParentName("parentUserService");
        context.registerBeanDefinition("childUserService", childBeanDefinition);
​
        context.refresh();
​
        System.out.println(context.getBean("childUserService"));
        System.out.println(context.getBean("childUserService"));
        System.out.println(context.getBean("childUserService"));
    }
}

这里的类说明下:

  • GenericBeanDefinition:推荐用这个,它有两个子类

    • AnnotatedGenericBeanDefinition:即annotatedBeanDefinitionReader.register(UserService.class)生成的BeanDefinition的类型

    • ScannedGenericBeanDefinition:即aClassPathBeanDefinitionScanner生成的BeanDefinition的类型

  • ChildBeanDefinition:注释上写了,大多数情况不需要用它,只用GenericBeanDefinition

  • RootBeanDefinition:合并过的BeanDefinition的类型

整个BeanDefinition的结构体系是很复杂的,这里只是点一下

BeanFactory和ApplicationContext

BeanFactory表示Bean工厂,所以很明显,BeanFactory会负责创建Bean,并且提供获取Bean的API。

而ApplicationContext是BeanFactory的一种,在Spring源码中,是这么定义的:

public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory,
        MessageSource, ApplicationEventPublisher, ResourcePatternResolver {
​
            ...
}

ApplicationContext继承了ListableBeanFactory和HierarchicalBeanFactory,而ListableBeanFactory和HierarchicalBeanFactory都继承了BeanFactory,所以我们可以认为ApplicationContext继承了BeanFactory,所以ApplicationContext也是BeanFactory的一种,拥有BeanFactory的所有功能。

不过ApplicationContext比BeanFactory更加强大,ApplicationContext还继承了其他接口,也就表示ApplicationContext还拥有其他功能,比如MessageSource表示国际化,ApplicationEventPublisher表示事件发布,EnvironmentCapable表示获取环境变量,等等,后面会马上介绍这些功能的使用。

在Spring的源码实现中,当我们new一个ApplicationContext时,其底层会new一个BeanFactory出来,当使用ApplicationContext的某些方法时,比如getBean(),底层调用的是BeanFactory的getBean()方法。

在Spring源码中,BeanFactory接口存在一个非常重要的实现类是:DefaultListableBeanFactory,也是非常核心的。具体重要性,随着后续课程会感受更深。

具体在父类GenericApplicationContext的构造器中构造

所以,我们也可以直接来使用DefaultListableBeanFactory

public class Main {
    public static void main(String[] args) {
​
        // spring容器 bean对象、beanDefinition、beanPostProcessor
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
​
        ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(beanFactory);
        scanner.scan("com.hexon");
​
        System.out.println(beanFactory.getBean("userService"));
    }
}
@Component
public class UserService {
​
    public void test() {
        System.out.println("UserService#test....");
    }
​
}

或者直接注册BeanDefinition:

public class Main {
    public static void main(String[] args) {
​
        // spring容器 bean对象、beanDefinition、beanPostProcessor
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
​
        // 这里UserService可以不加@Component
        BeanDefinition beanDefinition = new AnnotatedGenericBeanDefinition(UserService.class);
        beanFactory.registerBeanDefinition("userService", beanDefinition); 
​
        System.out.println(beanFactory.getBean("userService"));
    }
}

学习到这里,可以看一下DefaultListableBeanFactory类的继承图,假如你自己要实现一个Bean工厂,就可以根据实现接口来定制自己的Bean工厂所支持的行为:

值得一提的是HierarchicalBeanFactory接口,实现它可以实现BeanFactory之前的父子关系,有点类似类加载双亲委派。

获取运行时环境

这里获取环境变量其实是容器的一个附加功能,可以通过容器对象获取environment对象:

AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
ConfigurableEnvironment environment = applicationContext.getEnvironment();

这么理解,原本内置的有两个默认的环境变量域,但是也可以自己加入:

System.out.println(environment.getSystemEnvironment()); // 操作系统环境变量
System.out.println(environment.getSystemProperties());  // jvm 环境变量
​
for (PropertySource<?> propertySource : environment.getPropertySources()) {
    System.out.println(propertySource);
}

JVM参数可以这么配置:

另外,可以在配置类上添加注解引入配置文件:

@ComponentScan("com.hexon")
@PropertySource("classpath:application.properties")
public class MyConfig {
}

这里就多了一个可以查找环境变量的域。

值得一提的是,Environment接口可以直接注入,Spring默认会有一个这个类型的Bean。所以,用法就有很多种:

@Component
public class UserService {
​
    @Value("${k1}")  // 如果找不到会把${k1}字符串常量注入,所以需要添加对应的propertySource,但SpringBoot中可能更"智能"
    private String name;
​
    public void test() {
        System.out.println(name);
    }
​
}

你也可以直接@Autowired注入Environment对象再使用,也可以用ApplicationContextAware回调获取容器再获取Environment对象,看你自己怎么方便。

国际化

先定义配置文件:

默认有个本地的,再添加一个英语的;另外IDEA设置一下工程的编码

message.properties的内容为:

name=丹尼

message_en.properties的内容为:

name=danny

再增加一个配置Bean:

@ComponentScan("com.hexon")
public class MyConfig {
​
    @Bean
    public MessageSource messageSource() {
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
        messageSource.setBasename("message");
        return messageSource;
    }
​
}

有了这个Bean,你可以在你任意想要进行国际化的地方使用该MessageSource。

public class Main {
    public static void main(String[] args) {
​
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
​
        System.out.println(applicationContext.getMessage("name", null, Locale.ENGLISH));
        System.out.println(applicationContext.getMessage("name", null, Locale.getDefault()));
​
    }
}

这个在业务类中也同样像Environment一样可以先获取到容器再用,但也可以直接注入MessageSource类型使用。

另外,这个地方注意下中文乱码的问题,把工程编码,properties文件编码,CMD编码都改成UTF-8

Win10我是这么设置的

  1. Win + R,输入 intl.cpl

  2. 进入"管理"选项卡

  3. 点击"更改系统区域设置"

  4. 勾选"Beta 版:使用 Unicode UTF-8 提供全球语言支持"

  5. 点击"确定"并重启电脑

资源加载

ApplicationContext还拥有资源加载的功能,具体来说是因为ApplicationContext实现了ResourcePatternResolver接口。

比如,可以直接利用ApplicationContext获取某个文件的内容:

public class Main {
    public static void main(String[] args) throws IOException {
​
​
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
​
        Resource resource = context.getResource("file:///D:\\2025\\springsource\\spring-framework\\spring-demo\\src\\main\\resources\\spring.xml");
        System.out.println(resource.contentLength());
​
    }
}

也可以直接获取某个远程的资源:

public static void main(String[] args) throws IOException {
​
​
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
​
    Resource resource = context.getResource("https://www.baidu.com");
    System.out.println(resource.contentLength());
    System.out.println(resource.getURL());
​
}

还可以一次性获取多个:

public static void main(String[] args) throws IOException {
​
​
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
​
    Resource[] resources = context.getResources("classpath:com/hexon/*.class");
    for (Resource resource : resources) {
        System.out.println(resource.contentLength());
        System.out.println(resource.getFilename());
    }
​
}

这个在类扫描的时候就有用到

事件发布

先定义一个事件监听器

@Bean
public ApplicationListener<PayloadApplicationEvent<String>> applicationListener() {
    return new ApplicationListener<>() {
        @Override
        public void onApplicationEvent(PayloadApplicationEvent<String> event) {
            System.out.println("接收事件:" + event.getPayload());
        }
    };
}

然后使用容器对象发布一个事件:

public class Main {
    public static void main(String[] args) throws IOException {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
        context.publishEvent("hello event");
    }
}

类型转化

在Spring、SpringBoot、Spring Cloud中,有时根据beanName得到的Bean对象为A类型,但是需要的确实B类型,此时Spring就会找容器中是否有支持A类型转成B类型的类型转换器,如果有就会做类型转换,如果没有就会报错了。

先来看一段代码:

// User类定义
public class User {
​
    private String name;
​
    public String getName() {
        return name;
    }
​
    public void setName(String name) {
        this.name = name;
    }
}
// UserService类定义
@Component
public class UserService {
​
    // 直接把字符串注入User对象
    @Value("danny")
    private User user;
​
    public void test() {
        System.out.println(user.getName());
    }
​
}
// 配置类
@ComponentScan("com.hexon")
public class MyConfig {
​
​
}
// 测试
public class Main {
    public static void main(String[] args) throws IOException {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
        context.getBean(UserService.class).test();
    }
}
运行结果:
Caused by: java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'com.hexon.User': no matching editors or conversion strategy found

这个代码报错是正常的,但是从错误信息中我们可以看到,Spring默认似乎在找寻editor或者转换策略。

PropertyEditor

这其实是JDK中提供的类型转化工具类,可以把一个String转成其他类型,比如一下自定义PropertyEditor就是支持把一个String转成User对象。

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

直接测试:

public class Main {
    public static void main(String[] args) throws IOException {
        StringToUserPropertyEditor propertyEditor = new StringToUserPropertyEditor();
        propertyEditor.setAsText("danny");
        User user = (User) propertyEditor.getValue();
        System.out.println(user.getName()); // danny
    }
}

向Spring中注册PropertyEditor:

@Bean
public CustomEditorConfigurer customEditorConfigurer() {
​
    Map<Class<?>, Class<? extends PropertyEditor>> propertyEditorMap = new HashMap<>();
    propertyEditorMap.put(User.class, StringToUserPropertyEditor.class);
​
    // CustomEditorConfigurer是一个BeanFactoryPostProcessor
    CustomEditorConfigurer customEditorConfigurer = new CustomEditorConfigurer();
    customEditorConfigurer.setCustomEditors(propertyEditorMap);
​
    return customEditorConfigurer;
}

添加到配置类中。BeanFactoryPostProcessor后面介绍。

测试:

public class Main {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
        applicationContext.getBean(UserService.class).test();
    }
}

此时能成功转换,说明Spring找到了StringToUserPropertyEditor

Spring中内置了很多PropertyEditor,比如StringArrayPropertyEditor、PathEditor等。

但PropertyEditor属于 JavaBeans 时代的老机制,除非维护老项目,不推荐再使用。

ConversionService

PropertyEditor是GUI时代的产物了,理论上它也不是只能将String转其他类型,可以Object转Object。现代的Spring应该,已经使用Converter了,ConversionService则更强大,下面是一些概念的对比。

组件

作用

类似比喻

Converter

定义“怎么从 S → T 转换”

螺丝刀

ConversionService

管理转换器、查找转换器、执行转换

工具箱(选择正确的工具来干活)

能力

PropertyEditor

Converter / ConversionService

是否只能处理 String

✔ 在 Spring 实际用法中是

❌ 任意类型 S → T

是否线程安全

❌ 否

✔ 是(无状态)

是否可全局自动注册

❌ 需要 @InitBinder 等

✔ Spring Boot 自动配置

泛型支持

❌ 不友好

✔ 完整支持

API 现代化

❌ 旧(JavaBeans 时代遗留)

✔ Spring 专用现代体系

格式化支持

❌ 无

✔ Formatter 等扩展

MVC 配置绑定

✔ 有

✔ 更强大

先来看一个最简单的例子:

// @Component  // 纯SpringFramework情况下这个不需要加,你还是要自己添加到ConversionService中
public class StringToUserConverter implements Converter<String, User> {
​
    @Override
    public User convert(String source) {
        User user = new User();
        user.setId(Long.valueOf(source));
        user.setName("User-" + source);
        return user;
    }
}

这里用@Component在纯 Spring(XML/Java Config)中是不会生效的,Spring 不会扫描 Converter 并自动放入 ConversionService,Spring 也不会自动创建默认的 ConversionService。因此还要自己注册到 ConversionService,这配置类中添加下面代码:

@Bean
public ConversionService conversionService() {
    FormattingConversionService service = new FormattingConversionService();
    service.addConverter(new StringToUserConverter());
    return service;
}

此时再次测试就可以正常工作了:

public class Main {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
        applicationContext.getBean(UserService.class).test();
    }
}

值得一提的是,这个如果在SpringMVC中(非SpringBoot),也是要自己启用转换服务的:

@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
​
    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addConverter(new StringToUserConverter());
    }
}

而在SpringBoot中它会自动装配,你只要定义一个Converter的Bean。

下面来看更强大的ConditionalGenericConverter:

/**
 * 类型转换
 * Converter 是简单版,只处理固定类型转换。
 * ConditionalGenericConverter 是高级版,支持动态条件、支持复杂类型判断,所以必须实现三个方法。
 **/
public class StringToUserConverter implements ConditionalGenericConverter {
​
    // 判断“现在这个情况,我要不要处理”
    @Override
    public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
        return sourceType.getType().equals(String.class) && targetType.getType().equals(User.class);
    }
​
    // 告诉 Spring:我能做什么类型的转换
    @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;
    }
}

直接使用:

public static void main(String[] args) {
    DefaultConversionService conversionService = new DefaultConversionService();
    conversionService.addConverter(new StringToUserConverter());
    User user = conversionService.convert("danny", User.class);
    System.out.println(user.getName());
}

如何向Spring中注册ConversionService:

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

ConversionServiceFactoryBean看他注释和源码就知道它会创建一个DefaultConversionService,会带一些默认的Converter。

TypeConverter

整合了PropertyEditor和ConversionService的功能,Spring内部用的就是它

public class Main {
    public static void main(String[] args) {
        DefaultConversionService conversionService = new DefaultConversionService();
        conversionService.addConverter(new StringToUserConverter());
​
        SimpleTypeConverter typeConverter = new SimpleTypeConverter();
        typeConverter.registerCustomEditor(User.class, new StringToUserPropertyEditor());
        typeConverter.setConversionService(conversionService);
        User value = typeConverter.convertIfNecessary("danny", User.class);
        System.out.println(value.getName()); // 两个都有的情况,默认会走Editor
    }
}

OrderComparator和AnnotationAwareOrderComparator

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

比如:

public class A  implements Ordered {
    @Override
    public int getOrder() {
        return 2;
    }
}
public class B implements Ordered {
    @Override
    public int getOrder() {
        return 1;
    }
}
public class Main {
    public static void main(String[] args) {
        List<Object> list = new ArrayList<>();
        list.add(new A());
        list.add(new B());
​
        // 按order值升序排序
        list.sort(new OrderComparator());
​
        System.out.println(list); // B A
    }
}

源码分析:OrderComparator#doCompare

private int doCompare(@Nullable Object o1, @Nullable Object o2, @Nullable OrderSourceProvider sourceProvider) {
    boolean p1 = (o1 instanceof PriorityOrdered); // PriorityOrdered 是一个标记接口,实现了代码优先级最高
    boolean p2 = (o2 instanceof PriorityOrdered);
​
    // 只要有一个实现,另一个没有实现,则实现的优先级高,越大
    // p1 < p2
    if (p1 && !p2) {
        return -1;
    }
    // p2 < p1
    else if (p2 && !p1) {
        return 1;
    }
​
    // 都实现或者都没有实现PriorityOrdered就获取Ordered的值比较
    int i1 = getOrder(o1, sourceProvider);
    int i2 = getOrder(o2, sourceProvider);
    return Integer.compare(i1, i2);
}

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

@Order(2)
public class A {
​
}
@Order(1)
public class B {
​
}
public class Main {
    public static void main(String[] args) {
        List<Object> list = new ArrayList<>();
        list.add(new A());
        list.add(new B());
​
        // 按order值升序排序
        list.sort(new AnnotationAwareOrderComparator()); // 注意:要用AnnotationAwareOrderComparator
​
        System.out.println(list); // B A
    }
}

源码分析:

AnnotationAwareOrderComparator是继承了OrderComparator,OrderComparator实现了JDK的Comparator接口,所以list.sort会默认调用compare方法,而compare方法又调用了doCompare方法,所以最终还是先执行OrderComparator#doCompare,然后就会调用getOrder方法,getOrder方法中的sourceProvider是null,所以调用重载的getOrder方法,然后就调用到了findOrder方法因为this是AnnotationAwareOrderComparator类型,也就是调用AnnotationAwareOrderComparator#findOrder方法。通过阅读AnnotationAwareOrderComparator#findOrder方法的源码,就可以知道,如果实现了Ordered接口,就优先取这个接口方法返回的order值了,也就是说注解的值就没有效果了。再就调用AnnotationAwareOrderComparator#findOrderFromAnnotation方法。

后面的细节我暂时没有看明白,反正先知道,如果实现了Ordered接口,那么@Order注解的值就没有效果。