spring整合mybatis过程分析
version: '2.0.2'之前
实现原理:执行内置的BeanDefinitionRegistryPostProcessor在完成扫描后,处理扫描出来的beandrfinition当中的import的时候,实例化这个import对象,调用import进来的对象的方法
version: '2.0.2'及之后使用:
实现原理:spring执行内置的BeanDefinitionRegistryPostProcessor时候,注册了一个mybatis自己提供的BeanDefinitionRegistryPostProcessor实现类,接着执行mybatis提供的BeanDefinitionRegistryPostProcessor的方法,实例化一个扫描器。mybatis是BeanDefinitionRegistryPostProcessor的扩展点
依据依赖包
compile group: 'org.mybatis', name: 'mybatis-spring', version: '2.0.3'
@MapperScan可以不写,只要把MapperScannerConfigurer对象按下面方式或注解给spring容器
官方注释文档:
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="org.mybatis.spring.sample.mapper" />
<!-- optional unless there are multiple session factories defined -->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
</bean>
@Bean注解形式MapperScannerConfigurer类替代@MapperScan
package com.dao;
public interface UserDao {
@Select( "select name from users where id =2" )
public String query();
}
package com;
@Configuration
@ComponentScan("com.service")
//@MapperScan("com.dao")
public class AppConfig {
@Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName( "com.mysql.jdbc.Driver" );
dataSource.setUrl( "jdbc:mysql://127.0.0.1:3306/eshop" );
dataSource.setUsername( "root" );
dataSource.setPassword( "root" );
return dataSource;
}
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource() );
return factoryBean.getObject();
}
@Bean
public SqlSessionTemplate sqlSessionTemplate() throws Exception{
SqlSessionTemplate sqlSessionTemplate = new SqlSessionTemplate(sqlSessionFactory());
return sqlSessionTemplate;
}
@Bean
public MapperScannerConfigurer getMapperScannerConfigurer() throws Exception{
MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
mapperScannerConfigurer.setBasePackage( "com.dao" );
mapperScannerConfigurer.setSqlSessionTemplateBeanName( "sqlSessionTemplate" );
return mapperScannerConfigurer;
}
public static void main(String[] args) {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext( AppConfig.class );
UserDao userDao = ac.getBean(UserDao.class );
System.out.println( userDao.query() );
}
}
源码分析
执行时机是在spring执行内置的BeanDefinitionRegistryPostProcessor的方法时,会进行解析扫描
得到配置类上MapperScan注解的值,并且创建MapperScannerConfigurer类型的BeanDefinition
MapperScan_val.png
在执行MapperScannerConfigurer的postProcessBeanDefinitionRegistry方法时,就会进入ClassPathBeanDefinitionScanner#scan方法,最后注册basePackages路径下的类成为BeanDefinition
参考类
MapperScan注解类,
package org.mybatis.spring.annotation;
/**
* Use this annotation to register MyBatis mapper interfaces when using Java Config. It performs when same work as
* {@link MapperScannerConfigurer} via {@link MapperScannerRegistrar}.
*
* <p>
* Configuration example:
* </p>
*
* <pre class="code">
* @Configuration
* @MapperScan("org.mybatis.spring.sample.mapper")
* public class AppConfig {
*
* @Bean
* public DataSource dataSource() {
* return new EmbeddedDatabaseBuilder().addScript("schema.sql").build();
* }
*
* @Bean
* public DataSourceTransactionManager transactionManager() {
* return new DataSourceTransactionManager(dataSource());
* }
*
* @Bean
* public SqlSessionFactory sqlSessionFactory() throws Exception {
* SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
* sessionFactory.setDataSource(dataSource());
* return sessionFactory.getObject();
* }
* }
* </pre>
*
* @author Michael Lanyon
* @author Eduardo Macarron
*
* @since 1.2.0
* @see MapperScannerRegistrar
* @see MapperFactoryBean
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
/**
* Alias for the {@link #basePackages()} attribute. Allows for more concise annotation declarations e.g.:
* {@code @MapperScan("org.my.pkg")} instead of {@code @MapperScan(basePackages = "org.my.pkg"})}.
*
* @return base package names
*/
String[] value() default {};
/**
* Base packages to scan for MyBatis interfaces. Note that only interfaces with at least one method will be
* registered; concrete classes will be ignored.
*
* @return base package names for scanning mapper interface
*/
String[] basePackages() default {};
/**
* Type-safe alternative to {@link #basePackages()} for specifying the packages to scan for annotated components. The
* package of each class specified will be scanned.
* <p>
* Consider creating a special no-op marker class or interface in each package that serves no purpose other than being
* referenced by this attribute.
*
* @return classes that indicate base package for scanning mapper interface
*/
Class<?>[] basePackageClasses() default {};
/**
* The {@link BeanNameGenerator} class to be used for naming detected components within the Spring container.
*
* @return the class of {@link BeanNameGenerator}
*/
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
/**
* This property specifies the annotation that the scanner will search for.
* <p>
* The scanner will register all interfaces in the base package that also have the specified annotation.
* <p>
* Note this can be combined with markerInterface.
*
* @return the annotation that the scanner will search for
*/
Class<? extends Annotation> annotationClass() default Annotation.class;
/**
* This property specifies the parent that the scanner will search for.
* <p>
* The scanner will register all interfaces in the base package that also have the specified interface class as a
* parent.
* <p>
* Note this can be combined with annotationClass.
*
* @return the parent that the scanner will search for
*/
Class<?> markerInterface() default Class.class;
/**
* Specifies which {@code SqlSessionTemplate} to use in the case that there is more than one in the spring context.
* Usually this is only needed when you have more than one datasource.
*
* @return the bean name of {@code SqlSessionTemplate}
*/
String sqlSessionTemplateRef() default "";
/**
* Specifies which {@code SqlSessionFactory} to use in the case that there is more than one in the spring context.
* Usually this is only needed when you have more than one datasource.
*
* @return the bean name of {@code SqlSessionFactory}
*/
String sqlSessionFactoryRef() default "";
/**
* Specifies a custom MapperFactoryBean to return a mybatis proxy as spring bean.
*
* @return the class of {@code MapperFactoryBean}
*/
Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;
/**
* Whether enable lazy initialization of mapper bean.
*
* <p>
* Default is {@code false}.
* </p>
*
* @return set {@code true} to enable lazy initialization
* @since 2.0.2
*/
String lazyInitialization() default "";
}
MapperScannerRegistrar 导入的类
package org.mybatis.spring.annotation;
/**
* A {@link ImportBeanDefinitionRegistrar} to allow annotation configuration of MyBatis mapper scanning. Using
* an @Enable annotation allows beans to be registered via @Component configuration, whereas implementing
* {@code BeanDefinitionRegistryPostProcessor} will work for XML configuration.
*
* @author Michael Lanyon
* @author Eduardo Macarron
* @author Putthiphong Boonphong
*
* @see MapperFactoryBean
* @see ClassPathMapperScanner
* @since 1.2.0
*/
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
/**
* {@inheritDoc}
*
* @deprecated Since 2.0.2, this method not used never.
*/
@Override
@Deprecated
public void setResourceLoader(ResourceLoader resourceLoader) {
// NOP
}
/**
* {@inheritDoc}
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AnnotationAttributes mapperScanAttrs = AnnotationAttributes
.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
if (mapperScanAttrs != null) {
registerBeanDefinitions(mapperScanAttrs, registry, generateBaseBeanName(importingClassMetadata, 0));
}
}
void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
builder.addPropertyValue("processPropertyPlaceHolders", true);
Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
if (!Annotation.class.equals(annotationClass)) {
builder.addPropertyValue("annotationClass", annotationClass);
}
Class<?> markerInterface = annoAttrs.getClass("markerInterface");
if (!Class.class.equals(markerInterface)) {
builder.addPropertyValue("markerInterface", markerInterface);
}
Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
if (!BeanNameGenerator.class.equals(generatorClass)) {
builder.addPropertyValue("nameGenerator", BeanUtils.instantiateClass(generatorClass));
}
Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
builder.addPropertyValue("mapperFactoryBeanClass", mapperFactoryBeanClass);
}
String sqlSessionTemplateRef = annoAttrs.getString("sqlSessionTemplateRef");
if (StringUtils.hasText(sqlSessionTemplateRef)) {
builder.addPropertyValue("sqlSessionTemplateBeanName", annoAttrs.getString("sqlSessionTemplateRef"));
}
String sqlSessionFactoryRef = annoAttrs.getString("sqlSessionFactoryRef");
if (StringUtils.hasText(sqlSessionFactoryRef)) {
builder.addPropertyValue("sqlSessionFactoryBeanName", annoAttrs.getString("sqlSessionFactoryRef"));
}
List<String> basePackages = new ArrayList<>();
basePackages.addAll(
Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList()));
basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText)
.collect(Collectors.toList()));
basePackages.addAll(Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName)
.collect(Collectors.toList()));
String lazyInitialization = annoAttrs.getString("lazyInitialization");
if (StringUtils.hasText(lazyInitialization)) {
builder.addPropertyValue("lazyInitialization", lazyInitialization);
}
builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));
registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
}
private static String generateBaseBeanName(AnnotationMetadata importingClassMetadata, int index) {
return importingClassMetadata.getClassName() + "#" + MapperScannerRegistrar.class.getSimpleName() + "#" + index;
}
/**
* A {@link MapperScannerRegistrar} for {@link MapperScans}.
*
* @since 2.0.0
*/
static class RepeatingRegistrar extends MapperScannerRegistrar {
/**
* {@inheritDoc}
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AnnotationAttributes mapperScansAttrs = AnnotationAttributes
.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScans.class.getName()));
if (mapperScansAttrs != null) {
AnnotationAttributes[] annotations = mapperScansAttrs.getAnnotationArray("value");
for (int i = 0; i < annotations.length; i++) {
registerBeanDefinitions(annotations[i], registry, generateBaseBeanName(importingClassMetadata, i));
}
}
}
}
}
MapperScannerConfigurer类
package org.mybatis.spring.mapper;
/**
* BeanDefinitionRegistryPostProcessor that searches recursively starting from a base package for interfaces and
* registers them as {@code MapperFactoryBean}. Note that only interfaces with at least one method will be registered;
* concrete classes will be ignored.
* <p>
* This class was a {code BeanFactoryPostProcessor} until 1.0.1 version. It changed to
* {@code BeanDefinitionRegistryPostProcessor} in 1.0.2. See https://jira.springsource.org/browse/SPR-8269 for the
* details.
* <p>
* The {@code basePackage} property can contain more than one package name, separated by either commas or semicolons.
* <p>
* This class supports filtering the mappers created by either specifying a marker interface or an annotation. The
* {@code annotationClass} property specifies an annotation to search for. The {@code markerInterface} property
* specifies a parent interface to search for. If both properties are specified, mappers are added for interfaces that
* match <em>either</em> criteria. By default, these two properties are null, so all interfaces in the given
* {@code basePackage} are added as mappers.
* <p>
* This configurer enables autowire for all the beans that it creates so that they are automatically autowired with the
* proper {@code SqlSessionFactory} or {@code SqlSessionTemplate}. If there is more than one {@code SqlSessionFactory}
* in the application, however, autowiring cannot be used. In this case you must explicitly specify either an
* {@code SqlSessionFactory} or an {@code SqlSessionTemplate} to use via the <em>bean name</em> properties. Bean names
* are used rather than actual objects because Spring does not initialize property placeholders until after this class
* is processed.
* <p>
* Passing in an actual object which may require placeholders (i.e. DB user password) will fail. Using bean names defers
* actual object creation until later in the startup process, after all placeholder substitution is completed. However,
* note that this configurer does support property placeholders of its <em>own</em> properties. The
* <code>basePackage</code> and bean name properties all support <code>${property}</code> style substitution.
* <p>
* Configuration sample:
*
* <pre class="code">
* {@code
* <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
* <property name="basePackage" value="org.mybatis.spring.sample.mapper" />
* <!-- optional unless there are multiple session factories defined -->
* <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
* </bean>
* }
* </pre>
*
* @author Hunter Presnall
* @author Eduardo Macarron
*
* @see MapperFactoryBean
* @see ClassPathMapperScanner
*/
public class MapperScannerConfigurer
implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
private String basePackage;
private boolean addToConfig = true;
private String lazyInitialization;
private SqlSessionFactory sqlSessionFactory;
private SqlSessionTemplate sqlSessionTemplate;
private String sqlSessionFactoryBeanName;
private String sqlSessionTemplateBeanName;
private Class<? extends Annotation> annotationClass;
private Class<?> markerInterface;
private Class<? extends MapperFactoryBean> mapperFactoryBeanClass;
private ApplicationContext applicationContext;
private String beanName;
private boolean processPropertyPlaceHolders;
private BeanNameGenerator nameGenerator;
/**
* This property lets you set the base package for your mapper interface files.
* <p>
* You can set more than one package by using a semicolon or comma as a separator.
* <p>
* Mappers will be searched for recursively starting in the specified package(s).
*
* @param basePackage
* base package name
*/
public void setBasePackage(String basePackage) {
this.basePackage = basePackage;
}
/**
* Same as {@code MapperFactoryBean#setAddToConfig(boolean)}.
*
* @param addToConfig
* a flag that whether add mapper to MyBatis or not
* @see MapperFactoryBean#setAddToConfig(boolean)
*/
public void setAddToConfig(boolean addToConfig) {
this.addToConfig = addToConfig;
}
/**
* Set whether enable lazy initialization for mapper bean.
* <p>
* Default is {@code false}.
* </p>
*
* @param lazyInitialization
* Set the @{code true} to enable
* @since 2.0.2
*/
public void setLazyInitialization(String lazyInitialization) {
this.lazyInitialization = lazyInitialization;
}
/**
* This property specifies the annotation that the scanner will search for.
* <p>
* The scanner will register all interfaces in the base package that also have the specified annotation.
* <p>
* Note this can be combined with markerInterface.
*
* @param annotationClass
* annotation class
*/
public void setAnnotationClass(Class<? extends Annotation> annotationClass) {
this.annotationClass = annotationClass;
}
/**
* This property specifies the parent that the scanner will search for.
* <p>
* The scanner will register all interfaces in the base package that also have the specified interface class as a
* parent.
* <p>
* Note this can be combined with annotationClass.
*
* @param superClass
* parent class
*/
public void setMarkerInterface(Class<?> superClass) {
this.markerInterface = superClass;
}
/**
* Specifies which {@code SqlSessionTemplate} to use in the case that there is more than one in the spring context.
* Usually this is only needed when you have more than one datasource.
* <p>
*
* @deprecated Use {@link #setSqlSessionTemplateBeanName(String)} instead
*
* @param sqlSessionTemplate
* a template of SqlSession
*/
@Deprecated
public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
this.sqlSessionTemplate = sqlSessionTemplate;
}
/**
* Specifies which {@code SqlSessionTemplate} to use in the case that there is more than one in the spring context.
* Usually this is only needed when you have more than one datasource.
* <p>
* Note bean names are used, not bean references. This is because the scanner loads early during the start process and
* it is too early to build mybatis object instances.
*
* @since 1.1.0
*
* @param sqlSessionTemplateName
* Bean name of the {@code SqlSessionTemplate}
*/
public void setSqlSessionTemplateBeanName(String sqlSessionTemplateName) {
this.sqlSessionTemplateBeanName = sqlSessionTemplateName;
}
/**
* Specifies which {@code SqlSessionFactory} to use in the case that there is more than one in the spring context.
* Usually this is only needed when you have more than one datasource.
* <p>
*
* @deprecated Use {@link #setSqlSessionFactoryBeanName(String)} instead.
*
* @param sqlSessionFactory
* a factory of SqlSession
*/
@Deprecated
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
this.sqlSessionFactory = sqlSessionFactory;
}
/**
* Specifies which {@code SqlSessionFactory} to use in the case that there is more than one in the spring context.
* Usually this is only needed when you have more than one datasource.
* <p>
* Note bean names are used, not bean references. This is because the scanner loads early during the start process and
* it is too early to build mybatis object instances.
*
* @since 1.1.0
*
* @param sqlSessionFactoryName
* Bean name of the {@code SqlSessionFactory}
*/
public void setSqlSessionFactoryBeanName(String sqlSessionFactoryName) {
this.sqlSessionFactoryBeanName = sqlSessionFactoryName;
}
/**
* Specifies a flag that whether execute a property placeholder processing or not.
* <p>
* The default is {@literal false}. This means that a property placeholder processing does not execute.
*
* @since 1.1.1
*
* @param processPropertyPlaceHolders
* a flag that whether execute a property placeholder processing or not
*/
public void setProcessPropertyPlaceHolders(boolean processPropertyPlaceHolders) {
this.processPropertyPlaceHolders = processPropertyPlaceHolders;
}
/**
* The class of the {@link MapperFactoryBean} to return a mybatis proxy as spring bean.
*
* @param mapperFactoryBeanClass
* The class of the MapperFactoryBean
* @since 2.0.1
*/
public void setMapperFactoryBeanClass(Class<? extends MapperFactoryBean> mapperFactoryBeanClass) {
this.mapperFactoryBeanClass = mapperFactoryBeanClass;
}
/**
* {@inheritDoc}
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
/**
* {@inheritDoc}
*/
@Override
public void setBeanName(String name) {
this.beanName = name;
}
/**
* Gets beanNameGenerator to be used while running the scanner.
*
* @return the beanNameGenerator BeanNameGenerator that has been configured
* @since 1.2.0
*/
public BeanNameGenerator getNameGenerator() {
return nameGenerator;
}
/**
* Sets beanNameGenerator to be used while running the scanner.
*
* @param nameGenerator
* the beanNameGenerator to set
* @since 1.2.0
*/
public void setNameGenerator(BeanNameGenerator nameGenerator) {
this.nameGenerator = nameGenerator;
}
/**
* {@inheritDoc}
*/
@Override
public void afterPropertiesSet() throws Exception {
notNull(this.basePackage, "Property 'basePackage' is required");
}
/**
* {@inheritDoc}
*/
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
// left intentionally blank
}
/**
* {@inheritDoc}
*
* @since 1.0.2
*/
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
scanner.setAddToConfig(this.addToConfig);
scanner.setAnnotationClass(this.annotationClass);
scanner.setMarkerInterface(this.markerInterface);
scanner.setSqlSessionFactory(this.sqlSessionFactory);
scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
scanner.setResourceLoader(this.applicationContext);
scanner.setBeanNameGenerator(this.nameGenerator);
scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
if (StringUtils.hasText(lazyInitialization)) {
scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
}
scanner.registerFilters();
scanner.scan(
StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
/*
* BeanDefinitionRegistries are called early in application startup, before BeanFactoryPostProcessors. This means that
* PropertyResourceConfigurers will not have been loaded and any property substitution of this class' properties will
* fail. To avoid this, find any PropertyResourceConfigurers defined in the context and run them on this class' bean
* definition. Then update the values.
*/
private void processPropertyPlaceHolders() {
Map<String, PropertyResourceConfigurer> prcs = applicationContext.getBeansOfType(PropertyResourceConfigurer.class);
if (!prcs.isEmpty() && applicationContext instanceof ConfigurableApplicationContext) {
BeanDefinition mapperScannerBean = ((ConfigurableApplicationContext) applicationContext).getBeanFactory()
.getBeanDefinition(beanName);
// PropertyResourceConfigurer does not expose any methods to explicitly perform
// property placeholder substitution. Instead, create a BeanFactory that just
// contains this mapper scanner and post process the factory.
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
factory.registerBeanDefinition(beanName, mapperScannerBean);
for (PropertyResourceConfigurer prc : prcs.values()) {
prc.postProcessBeanFactory(factory);
}
PropertyValues values = mapperScannerBean.getPropertyValues();
this.basePackage = updatePropertyValue("basePackage", values);
this.sqlSessionFactoryBeanName = updatePropertyValue("sqlSessionFactoryBeanName", values);
this.sqlSessionTemplateBeanName = updatePropertyValue("sqlSessionTemplateBeanName", values);
this.lazyInitialization = updatePropertyValue("lazyInitialization", values);
}
this.basePackage = Optional.ofNullable(this.basePackage).map(getEnvironment()::resolvePlaceholders).orElse(null);
this.sqlSessionFactoryBeanName = Optional.ofNullable(this.sqlSessionFactoryBeanName)
.map(getEnvironment()::resolvePlaceholders).orElse(null);
this.sqlSessionTemplateBeanName = Optional.ofNullable(this.sqlSessionTemplateBeanName)
.map(getEnvironment()::resolvePlaceholders).orElse(null);
this.lazyInitialization = Optional.ofNullable(this.lazyInitialization).map(getEnvironment()::resolvePlaceholders)
.orElse(null);
}
private Environment getEnvironment() {
return this.applicationContext.getEnvironment();
}
private String updatePropertyValue(String propertyName, PropertyValues values) {
PropertyValue property = values.getPropertyValue(propertyName);
if (property == null) {
return null;
}
Object value = property.getValue();
if (value == null) {
return null;
} else if (value instanceof String) {
return value.toString();
} else if (value instanceof TypedStringValue) {
return ((TypedStringValue) value).getValue();
} else {
return null;
}
}
}
网友评论