PropertyPlaceholderConfigurer

经常需要将配置信息配置在properties文件中,然后在XML文件中以占位符的方式引用。

实现思路很简单,在bean实例化之前,编辑BeanDefinition,解析XML文件中的占位符,然后用properties文件中的配置值替换占位符。而BeanFactoryPostProcessor具有编辑BeanDefinition的能力,因此PropertyPlaceholderConfigurer继承自BeanFactoryPostProcessor。

测试: car.properties

1
brand=lamborghini
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd">

<bean class="org.springframework.beans.factory.PropertyPlaceholderConfigurer">
<property name="location" value="classpath:car.properties" />
</bean>

<bean id="car" class="org.springframework.test.bean.Car">
<property name="brand" value="${brand}" />
</bean>

</beans>
1
2
3
4
5
6
7
8
9
10
public class PropertyPlaceholderConfigurerTest {

@Test
public void test() throws Exception {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:property-placeholder-configurer.xml");

Car car = applicationContext.getBean("car", Car.class);
assertThat(car.getBrand()).isEqualTo("lamborghini");
}
}

包扫描

结合bean的生命周期,包扫描只不过是扫描特定注解的类,提取类的相关信息组装成BeanDefinition注册到容器中。

在XmlBeanDefinitionReader中解析<context:component-scan />标签,扫描类组装BeanDefinition然后注册到容器中的操作在ClassPathBeanDefinitionScanner#doScan中实现。

测试:

1
2
3
4
@Component
public class Car {

}

package-scan.xml

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd">

<context:component-scan base-package="org.springframework.test.bean"/>

</beans>
1
2
3
4
5
6
7
8
9
10
public class PackageScanTest {

@Test
public void testScanPackage() throws Exception {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:package-scan.xml");

Car car = applicationContext.getBean("car", Car.class);
assertThat(car).isNotNull();
}
}

@Value注解

注解@Value和@Autowired通过BeanPostProcessor处理。InstantiationAwareBeanPostProcessor增加postProcessPropertyValues方法,在bean实例化之后设置属性之前执行,查看AbstractAutowireCapableBeanFactory#doCreateBean方法。

增加AutowiredAnnotationBeanPostProcessor用于处理注解@Value,@Autowired的处理在下一节实现,在ClassPathBeanDefinitionScanner#doScan将其添加到容器中。查看AutowiredAnnotationBeanPostProcessor#postProcessPropertyValues,其中字符解析器StringValueResolver在PropertyPlaceholderConfigurer中添加到BeanFactory中。

测试:

1
2
3
4
5
6
@Component
public class Car {

@Value("${brand}")
private String brand;
}

value-annotation.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd">

<bean class="org.springframework.beans.factory.PropertyPlaceholderConfigurer">
<property name="location" value="classpath:car.properties" />
</bean>

<context:component-scan base-package="org.springframework.test.bean"/>

</beans>

car.properties

1
brand=lamborghini
1
2
3
4
5
6
7
8
9
10
public class ValueAnnotationTest {

@Test
public void testValueAnnotation() throws Exception {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:value-annotation.xml");

Car car = applicationContext.getBean("car", Car.class);
assertThat(car.getBrand()).isEqualTo("lamborghini");
}
}

@Autowired注解

@Autowired注解的处理见AutowiredAnnotationBeanPostProcessor#postProcessPropertyValues

测试:

1
2
3
4
5
6
7
8
9
10
11
@Component
public class Car {

}

@Component
public class Person implements InitializingBean, DisposableBean {

@Autowired
private Car car;
}

autowired-annotation.xml

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd">

<context:component-scan base-package="org.springframework.test.bean"/>

</beans>
1
2
3
4
5
6
7
8
9
10
public class AutowiredAnnotationTest {

@Test
public void testAutowiredAnnotation() throws Exception {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:autowired-annotation.xml");

Person person = applicationContext.getBean(Person.class);
assertThat(person.getCar()).isNotNull();
}
}

bugfix:没有为代理bean设置属性

问题现象:没有为代理bean设置属性

问题原因:织入逻辑在InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation中执行,而该方法如果返回非null,会导致”短路”,不会执行后面的设置属性逻辑。因此如果该方法中返回代理bean后,不会为代理bean设置属性。

修复方案:跟spring保持一致,将织入逻辑迁移到BeanPostProcessor#postProcessAfterInitialization,即将DefaultAdvisorAutoProxyCreator#postProcessBeforeInstantiation的内容迁移到DefaultAdvisorAutoProxyCreator#postProcessAfterInitialization中。

顺便完善spring的扩展机制,为InstantiationAwareBeanPostProcessor增加postProcessAfterInstantiation方法,该方法在bean实例化之后设置属性之前执行。

至此,bean的生命周期比较完整了,如下:

img

测试: populate-proxy-bean-with-property-values.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd">

<bean id="worldService" class="org.springframework.test.service.WorldServiceImpl">
<property name="name" value="earth"/>
</bean>

<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>

<bean id="pointcutAdvisor" class="org.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor">
<property name="expression" value="execution(* org.springframework.test.service.WorldService.explode(..))"/>
<property name="advice" ref="methodInterceptor"/>
</bean>


<bean id="methodInterceptor" class="org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor">
<property name="advice" ref="beforeAdvice"/>
</bean>

<bean id="beforeAdvice" class="org.springframework.test.common.WorldServiceBeforeAdvice"/>

</beans>
1
2
3
4
5
6
7
8
9
10
11
public class WorldServiceImpl implements WorldService {

private String name;

@Override
public void explode() {
System.out.println("The " + name + " is going to explode");
}

//setter and getter
}
1
2
3
4
5
6
7
8
9
10
11
12
public class AutoProxyTest {

@Test
public void testPopulateProxyBeanWithPropertyValues() throws Exception {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:populate-proxy-bean-with-property-values.xml");

//获取代理对象
WorldService worldService = applicationContext.getBean("worldService", WorldService.class);
worldService.explode();
assertThat(worldService.getName()).isEqualTo("earth");
}
}

类型转换(一)

spring在org.springframework.core.convert.converter包中定义了三种类型转换器接口:Converter、ConverterFactory、GenericConverter。

一、Converter

1
2
3
4
5
6
7
public interface Converter<S, T> {

/**
* 类型转换
*/
T convert(S source);
}

Converter能将S类型的对象转换为T类型的对象,比如将String类型的对象转换为Integer类型的对象的实现类:

1
2
3
4
5
6
public class StringToIntegerConverter implements Converter<String, Integer> {
@Override
public Integer convert(String source) {
return Integer.valueOf(source);
}
}

使用:

1
Integer num = new StringToIntegerConverter().convert("8888");

二、ConverterFactory

1
2
3
4
public interface ConverterFactory<S, R> {

<T extends R> Converter<S, T> getConverter(Class<T> targetType);
}

Converter<S,T>接口适合一对一的类型转换,如果要将String类型转换为Ineger/Long/Float/Double/Decimal等类型,就要实现一系列的StringToInteger/StringToLongConverter/StringToFloatConverter转换器,非常不优雅。

ConverterFactory接口则适合一对多的类型转换,可以将一种类型转换为另一种类型及其子类。比如将String类型转换为Ineger/Long/Float/Double/Decimal等Number类型时,只需定义一个ConverterFactory转换器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class StringToNumberConverterFactory implements ConverterFactory<String, Number> {

@Override
public <T extends Number> Converter<String, T> getConverter(Class<T> targetType) {
return new StringToNumber<T>(targetType);
}

private static final class StringToNumber<T extends Number> implements Converter<String, T> {

private final Class<T> targetType;

public StringToNumber(Class<T> targetType) {
this.targetType = targetType;
}

@Override
public T convert(String source) {
if (source.length() == 0) {
return null;
}

if (targetType.equals(Integer.class)) {
return (T) Integer.valueOf(source);
} else if (targetType.equals(Long.class)) {
return (T) Long.valueOf(source);
}
//TODO 其他数字类型

else {
throw new IllegalArgumentException(
"Cannot convert String [" + source + "] to target class [" + targetType.getName() + "]");
}
}
}

}

使用:

1
2
3
StringToNumberConverterFactory converterFactory = new StringToNumberConverterFactory();
Converter<String, Integer> stringToIntegerConverter = converterFactory.getConverter(Integer.class);
Integer num = stringToIntegerConverter.convert("8888");

三、GenericConverter

1
2
3
4
5
6
public interface GenericConverter {

Set<ConvertiblePair> getConvertibleTypes();

Object convert(Object source, Class sourceType, Class targetType);
}

String类型转换为Boolean类型的实现类:

1
2
3
4
5
6
7
8
9
10
11
public class StringToBooleanConverter implements GenericConverter {
@Override
public Set<ConvertiblePair> getConvertibleTypes() {
return Collections.singleton(new ConvertiblePair(String.class, Boolean.class));
}

@Override
public Object convert(Object source, Class sourceType, Class targetType) {
return Boolean.valueOf((String) source);
}
}

使用:

1
Boolean flag = new StringToBooleanConverter().convert("true", String.class, Boolean.class);

ConversionService是类型转换体系的核心接口,将以上三种类型转换器整合到一起,GenericConversionService是其实现类,DefaultConversionService在GenericConversionService的基础上添加内置转换器。

测试见TypeConversionFirstPartTest。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public class TypeConversionFirstPartTest {

@Test
public void testStringToIntegerConverter() throws Exception {
StringToIntegerConverter converter = new StringToIntegerConverter();
Integer num = converter.convert("8888");
assertThat(num).isEqualTo(8888);
}

@Test
public void testStringToNumberConverterFactory() throws Exception {
StringToNumberConverterFactory converterFactory = new StringToNumberConverterFactory();

Converter<String, Integer> stringToIntegerConverter = converterFactory.getConverter(Integer.class);
Integer intNum = stringToIntegerConverter.convert("8888");
assertThat(intNum).isEqualTo(8888);

Converter<String, Long> stringToLongConverter = converterFactory.getConverter(Long.class);
Long longNum = stringToLongConverter.convert("8888");
assertThat(longNum).isEqualTo(8888L);
}

@Test
public void testGenericConverter() throws Exception {
StringToBooleanConverter converter = new StringToBooleanConverter();

Boolean flag = (Boolean) converter.convert("true", String.class, Boolean.class);
assertThat(flag).isTrue();
}

@Test
public void testGenericConversionService() throws Exception {
GenericConversionService conversionService = new GenericConversionService();
conversionService.addConverter(new StringToIntegerConverter());

Integer intNum = conversionService.convert("8888", Integer.class);
assertThat(conversionService.canConvert(String.class, Integer.class)).isTrue();
assertThat(intNum).isEqualTo(8888);

conversionService.addConverterFactory(new StringToNumberConverterFactory());
assertThat(conversionService.canConvert(String.class, Long.class)).isTrue();
Long longNum = conversionService.convert("8888", Long.class);
assertThat(longNum).isEqualTo(8888L);

conversionService.addConverter(new StringToBooleanConverter());
assertThat(conversionService.canConvert(String.class, Boolean.class)).isTrue();
Boolean flag = conversionService.convert("true", Boolean.class);
assertThat(flag).isTrue();
}
}

类型转换(二)

上一节实现了spring中的类型转换体系,本节将类型转换的能力整合到容器中。

为了方便使用,提供了创建ConversionService的FactoryBean——ConversionServiceFactoryBean。

如果有定义ConversionService,在AbstractApplicationContext#finishBeanFactoryInitialization方法中设置到容器中。

类型转换的时机有两个:

  • 为bean填充属性时,见AbstractAutowireCapableBeanFactory#applyPropertyValues
  • 处理@Value注解时,见AutowiredAnnotationBeanPostProcessor#postProcessPropertyValues

你可能会有疑问,如果没有定义ConversionService,是怎么进行基本类型的转换的?其实spring为了向下兼容保留了一套比较旧的类型转换机制,没有定义ConversionService时会使用其进行基本类型的转换工作,不必关注旧的类型转换机制。

测试:

1
2
3
4
5
6
public class Car {

private int price;

private LocalDate produceDate;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
public class StringToLocalDateConverter implements Converter<String, LocalDate> {

private final DateTimeFormatter DATE_TIME_FORMATTER;

public StringToLocalDateConverter(String pattern) {
DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern(pattern);
}

@Override
public LocalDate convert(String source) {
return LocalDate.parse(source, DATE_TIME_FORMATTER);
}
}

type-conversion-second-part.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd">

<bean id="car" class="org.springframework.test.bean.Car">
<property name="price" value="1000000"/>
<property name="produceDate" value="2021-01-01"/>
</bean>

<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters" ref="converters"/>
</bean>

<bean id="converters" class="org.springframework.test.common.ConvertersFactoryBean"/>

</beans>
1
2
3
4
5
6
7
8
9
10
11
public class TypeConversionSecondPartTest {

@Test
public void testConversionService() throws Exception {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:type-conversion-second-part.xml");

Car car = applicationContext.getBean("car", Car.class);
assertThat(car.getPrice()).isEqualTo(1000000);
assertThat(car.getProduceDate()).isEqualTo(LocalDate.of(2021, 1, 1));
}
}

本站由 卡卡龙 使用 Stellar 1.29.1主题创建

本站访问量 次. 本文阅读量 次.