spring property value 引用properties文件和直接设置的区别

spring property value 引用properties文件和直接设置的区别,第1张

1.PropertyPlaceholderConfigurer类 它是把属性中的定义的变量(var)替代,spring的配置文件中使用${var}的占位符 db.properties ${jdbc.driverClassName} ${jdbc.url} ${jdbc.username} ${jdbc.password} db.properties文件 jdbc.driverClassName.

在spring容器初始化的时候,BeanDefinition中配置的bean的属性值一般都为String类型,如何将String类型转换为Bean中属性对应的类型呢,在这个过程中就需要用到类型转换器了。spring实例化bean过程中对属性值的转换主要是使用BeanWrapperImpl实现的。

首先来看下BeanWrapperImpl的使用

定义一个处理日期的转换器

创建一个BeanWrapperImpl用于包装目标bean(这里来模拟spring的内部实现)。然后注册Date类型的转换器,将值使用DatePropertyEditor转换为Date类型。调用setPropertyValue的时候,给testBean的birthday字段设置一个字符串类型的时间,在实际赋值的过程中会调用到类型转换器将字符串转换为日期类型。

同样可以实现一个String trim的转换器

BeanWrapper支持嵌套属性的赋值,当存在嵌套属性的时候需要设置 setAutoGrowNestedPaths=true。

接下来我们就来研究一下BeanWrapperImpl的实现过程

java.beans包中声明了一个PropertyEditor接口,用于提供对属性的访问和赋值。

PropertyEditor有个默认实现类PropertyEditorSupport,如果要自定义属性转换器,直接继承该类,然后覆写setAsText、getAsText、setValue、getValue方法。在类型转换的时候如果值是字符串会调用setAsText来赋值value,其他情况下会调用setValue来赋值value,然后再调用getValue获取改变后的value值完成类型转换。相当于将PropertyEditor当做一个加工作坊,传进去一个值,返回想要类型的值

注意 :PropertyEditor是线程不安全的,有状态的,因此每次使用时都需要创建一个,不可重用;

首先先看下BeanWrapperImpl的继承图。还有一个跟BeanWrapperImpl平级的实现类DirectFieldAccessor,用于处理getter和setter的字段访问

从图可以看出,BeanWrapperImpl实现了3个顶级接口,提供了对象属性访问、类型转换器的注册和查找、类型转换功能

从继承图可以看到PropertyEditorRegistrySupport实现了PropertyEditorRegistry接口,该类默认实现了类型转换器的注册和查找功能

在PropertyEditorRegistrySupport声明了多个存储结构用于存储不同的类型转换器来源。这里需要注意的是PropertyEditor是存在在Map中的,目标类型作为key,所以对于一个类型只能注册一个PropertyEditor,后面注册的会覆盖前面注册的

同时还看到一个conversionService变量,spring提供了另一种类型转换接口Converter,通过conversionService调用对应的Converter进行类型转换,在PropertyEditorRegistrySupport同样可以注册进来conversionService,用于使用Converter进行类型转换。conversionService的详细使用会在下篇文章中讲到。

接下来来看PropertyEditor的注册过程。

在上面提到过PropertyEditor存在在一个Map中,key是目标类型,那么这个参数propertyPath是干嘛的呢?这是为了给属性名指定专属的类型的转换器。因为一个目标类型只能有一个PropertyEditor的限制。但是有时候确实某个属性的类型转换比较特殊,这个时候就可以给这个属性名单独注册一个类型转换器,不会覆盖其他的哦。在类型转换的时候,会先根据属性名去customEditorsForPath中找可以用的PropertyEditor。

来看PropertyEditor的查找流程

首先根据属性名从customEditorsForPath查找特定的类型转换器

根据类型查找定义的类型转换器,如果没有对应的,则查找父类对应的类型转换器

PropertyEditorRegistrySupport 中默认添加了一些转换器,当调用 getDefaultEditor(requiredType)的时候会进行注册

TypeConverter接口定义了将一个值转换为目标类型的值的功能。在继承图中可以看出TypeConverterSupport对类型转换提供了默认实现。

TypeConverterSupport将类型转换功能委托给typeConverterDelegate实现

TypeConverterDelegate实现了类型转换的功能,创建TypeConverterDelegate的时候需要一个propertyEditorRegistry对象,用于查找匹配的类型转换器

首先通过propertyEditorRegistry查找自定义的类型转换器PropertyEditor和ConversionService

当该类型没有注册自定义的PropertyEditor,并且存在conversionService的时候,使用conversionService进行类型转换。如果conversionService中没有配置对应的converter,那么继续尝试使用默认注册的PropertyEditor。

如果目标类型存在自定义PropertyEditor 或者 目标类型和值类型不一样则需要进行类型转换(当没有找到ProperEditor的时候会尝试查找默认注册的PropertyEditor)

类型转换,主要分三种情况处理,值类型不是字符串,值类型是字符串数组,值类型是字符串

最后,需要对转换后的值和目标类型进行判断,是否符合要求,如果不符合继续处理。这里主要处理了集合类型还有Number类型、基本类型转String、枚举类型。

在attemptToConvertStringToEnum方法中自动根据枚举的名称转换为枚举的对象

PropertyAccessor接口定义了属性访问的功能。通过实现setPropertyValue 和 getPropertyValue方法实现对象属性的赋值和访问。

AbstractPropertyAccessor实现了PropertyAccessor接口,不过没有对setPropertyValue和getPropertyValue进行实现,而是单独提供了一个 setPropertyValues的方法, 用于批量设置属性值,同时可以通过 ignoreUnknown和ignoreInvalid参数忽略未知的属性

在AbstractNestablePropertyAccessor类中实现了setPropertyValue和getPropertyValue功能。

由于存在嵌套属性赋值的情况,对于嵌套的处理,其实只需要对嵌套的最底层进行类型转换,上层每一层就创建默认的值然后set到再上层对象的属性中。在这里spring使用了递归解决这个问题,创建每一层属性的对象值,使用BeanWrapper包装该对象,那么又是一个BeanWrapperImpl的赋值流程。

来看这个递归的内部实现,在getNestedPropertyAccessor完成对外层属性的初始化和将该值赋值到所依赖的对象中。然后使用BeanWrapper封装属性对象,后续走属性对象的赋值流程

创建属性对象,并将该对象set到宿主对象。因为对象是指针引用的,所以在这步已经完成对宿主对象的属性赋值,接下来的流程只要对属性对象中的依赖属性进行赋值。

使用递归解决了嵌套赋值的问题,那么接下来就是针对最底层BeanWrapperImpl的属性赋值流程

在processLocalProperty方法中,首先通过子类获取属性处理器,通过PropertyHandler对属性赋值。在赋值之前再次判断属性是否已经进行了类型转换,如果没有再次调用类型转换器进行转换,如果已经完成类型转换,使用ConvertedValue

对属性的访问和设置spring进行了更小粒度的封装。提供了 PropertyHandler抽象类。为什么在这里进行抽象,看PropertyHandler的两个实现,可以看到一个是BeanPropertyHandler,一个是FieldPropertyHandler,不难想象,属性一种是由getter和setter方法进行访问,一种是没有getter和setter直接反射字段进行的。

如果要对map进行控制,我们可以再提供一个专门处理map的实现了类handler

BeanWrapperImpl提供了BeanPropertyHandler,将setter和getter传入

BeanPropertyHandler提供对setter和getter的访问

AbstractNestablePropertyAccessor的另一个实现类DirectFieldAccessor,专门用于给字段赋值,不依赖setter和getter,那么这个是怎么实现的,看源码发现是DirectFieldAccessor中提供了一个PropertyHandler的实现类,通过Field的反射实现了setValue和getValue

1、application.properties需设置spring.jpa.hibernate.ddl-auto=create;

2、springboot会通过实体类创建表;

3、再执行import.sql。

所以,只要有实体类user存在,则可以在import.sql中直接使用insert语句插入数据到user表即可,不需要在import.sql中定义创建user表的ddl语句。(import.sql中是可以定义create等ddl语句的,不单只能insert *** 作。)

1、在application.properties修改以下内容:

2、在src/main/resources目录下新建import.sql(名字一定要是import.sql),内容如下

3、启动应用,进行初始化数据

4、启动完成后,停止应用,修改spring.jpa.hibernate.ddl-auto=create成spring.jpa.hibernate.ddl-auto=update后,再次启动应用。即spring.jpa.hibernate.ddl-auto设置成update时,import.sql不会执行。

缺点:第一次执行,导入数据后。需要将spring.jpa.hibernate.ddl-auto=create设置成spring.jpa.hibernate.ddl-auto=update。不然第二次执行,会因spring.jpa.hibernate.ddl-auto设置是create值,先drop表,再创建表,导致第一次启动应用后的数据丢失。(比如,新增了另一个用户,就这样在第二次启动应用丢失了)

1、在application.properties新增以下内容:

2、在src/main/resources目录下新建schema.sql(名字可以自定义)、data.sql(名字可以自定义)。

3、启动应用,进行初始化数据

4、启动完成后,停止应用,步骤一设置的 spring.datasource.initialization-mode 等注释掉。

再次启动应用。使用jdbc方式,不跟spring.jpa.hibernate.ddl-auto值有关系,无论是create和update都不影响jdbc这种初始化方式。

第一种方式启动的时候jpa会根据实体类创建表,import.sql负责初始化数据(insert等 *** 作)。

第二种方式启动的时候,不会创建表,需要在初始化脚本schema.sql,data.sql中判断是否存在表,再初始化脚本中的步骤。

如果使用jpa的话,那就选择第一种方式;如果使用的mybatis,则可选择第二种方式。


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/tougao/7987476.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-04-12
下一篇 2023-04-12

发表评论

登录后才能评论

评论列表(0条)

保存