View Javadoc
1   /**
2    * Copyright 2010-2020 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *    http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.mybatis.spring;
17  
18  import javax.sql.DataSource;
19  import java.io.IOException;
20  import java.lang.reflect.Modifier;
21  import java.sql.SQLException;
22  import java.util.HashSet;
23  import java.util.Optional;
24  import java.util.Properties;
25  import java.util.Set;
26  import java.util.stream.Stream;
27  
28  import org.apache.ibatis.builder.xml.XMLConfigBuilder;
29  import org.apache.ibatis.builder.xml.XMLMapperBuilder;
30  import org.apache.ibatis.cache.Cache;
31  import org.apache.ibatis.executor.ErrorContext;
32  import org.apache.ibatis.io.Resources;
33  import org.apache.ibatis.io.VFS;
34  import org.apache.ibatis.mapping.DatabaseIdProvider;
35  import org.apache.ibatis.mapping.Environment;
36  import org.apache.ibatis.plugin.Interceptor;
37  import org.apache.ibatis.reflection.factory.ObjectFactory;
38  import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;
39  import org.apache.ibatis.scripting.LanguageDriver;
40  import org.apache.ibatis.session.Configuration;
41  import org.apache.ibatis.session.SqlSessionFactory;
42  import org.apache.ibatis.session.SqlSessionFactoryBuilder;
43  import org.apache.ibatis.transaction.TransactionFactory;
44  import org.apache.ibatis.type.TypeHandler;
45  import org.mybatis.logging.Logger;
46  import org.mybatis.logging.LoggerFactory;
47  import org.mybatis.spring.transaction.SpringManagedTransactionFactory;
48  import org.springframework.beans.factory.FactoryBean;
49  import org.springframework.beans.factory.InitializingBean;
50  import org.springframework.context.ApplicationEvent;
51  import org.springframework.context.ApplicationListener;
52  import org.springframework.context.ConfigurableApplicationContext;
53  import org.springframework.context.event.ContextRefreshedEvent;
54  import org.springframework.core.NestedIOException;
55  import org.springframework.core.io.Resource;
56  import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
57  import org.springframework.core.io.support.ResourcePatternResolver;
58  import org.springframework.core.type.ClassMetadata;
59  import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
60  import org.springframework.core.type.classreading.MetadataReaderFactory;
61  import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy;
62  import org.springframework.util.ClassUtils;
63  
64  import static org.springframework.util.Assert.notNull;
65  import static org.springframework.util.Assert.state;
66  import static org.springframework.util.ObjectUtils.isEmpty;
67  import static org.springframework.util.StringUtils.hasLength;
68  import static org.springframework.util.StringUtils.tokenizeToStringArray;
69  
70  /**
71   * {@code FactoryBean} that creates a MyBatis {@code SqlSessionFactory}. This is the usual way to set up a shared
72   * MyBatis {@code SqlSessionFactory} in a Spring application context; the SqlSessionFactory can then be passed to
73   * MyBatis-based DAOs via dependency injection.
74   *
75   * Either {@code DataSourceTransactionManager} or {@code JtaTransactionManager} can be used for transaction demarcation
76   * in combination with a {@code SqlSessionFactory}. JTA should be used for transactions which span multiple databases or
77   * when container managed transactions (CMT) are being used.
78   *
79   * @author Putthiphong Boonphong
80   * @author Hunter Presnall
81   * @author Eduardo Macarron
82   * @author EddĂș MelĂ©ndez
83   * @author Kazuki Shimizu
84   *
85   * @see #setConfigLocation
86   * @see #setDataSource
87   */
88  public class SqlSessionFactoryBean
89      implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
90  
91    private static final Logger LOGGER = LoggerFactory.getLogger(SqlSessionFactoryBean.class);
92  
93    private static final ResourcePatternResolver RESOURCE_PATTERN_RESOLVER = new PathMatchingResourcePatternResolver();
94    private static final MetadataReaderFactory METADATA_READER_FACTORY = new CachingMetadataReaderFactory();
95  
96    private Resource configLocation;
97  
98    private Configuration configuration;
99  
100   private Resource[] mapperLocations;
101 
102   private DataSource dataSource;
103 
104   private TransactionFactory transactionFactory;
105 
106   private Properties configurationProperties;
107 
108   private SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
109 
110   private SqlSessionFactory sqlSessionFactory;
111 
112   // EnvironmentAware requires spring 3.1
113   private String environment = SqlSessionFactoryBean.class.getSimpleName();
114 
115   private boolean failFast;
116 
117   private Interceptor[] plugins;
118 
119   private TypeHandler<?>[] typeHandlers;
120 
121   private String typeHandlersPackage;
122 
123   private Class<?>[] typeAliases;
124 
125   private String typeAliasesPackage;
126 
127   private Class<?> typeAliasesSuperType;
128 
129   private LanguageDriver[] scriptingLanguageDrivers;
130 
131   private Class<? extends LanguageDriver> defaultScriptingLanguageDriver;
132 
133   // issue #19. No default provider.
134   private DatabaseIdProvider databaseIdProvider;
135 
136   private Class<? extends VFS> vfs;
137 
138   private Cache cache;
139 
140   private ObjectFactory objectFactory;
141 
142   private ObjectWrapperFactory objectWrapperFactory;
143 
144   /**
145    * Sets the ObjectFactory.
146    *
147    * @since 1.1.2
148    * @param objectFactory
149    *          a custom ObjectFactory
150    */
151   public void setObjectFactory(ObjectFactory objectFactory) {
152     this.objectFactory = objectFactory;
153   }
154 
155   /**
156    * Sets the ObjectWrapperFactory.
157    *
158    * @since 1.1.2
159    * @param objectWrapperFactory
160    *          a specified ObjectWrapperFactory
161    */
162   public void setObjectWrapperFactory(ObjectWrapperFactory objectWrapperFactory) {
163     this.objectWrapperFactory = objectWrapperFactory;
164   }
165 
166   /**
167    * Gets the DatabaseIdProvider
168    *
169    * @since 1.1.0
170    * @return a specified DatabaseIdProvider
171    */
172   public DatabaseIdProvider getDatabaseIdProvider() {
173     return databaseIdProvider;
174   }
175 
176   /**
177    * Sets the DatabaseIdProvider. As of version 1.2.2 this variable is not initialized by default.
178    *
179    * @since 1.1.0
180    * @param databaseIdProvider
181    *          a DatabaseIdProvider
182    */
183   public void setDatabaseIdProvider(DatabaseIdProvider databaseIdProvider) {
184     this.databaseIdProvider = databaseIdProvider;
185   }
186 
187   /**
188    * Gets the VFS.
189    *
190    * @return a specified VFS
191    */
192   public Class<? extends VFS> getVfs() {
193     return this.vfs;
194   }
195 
196   /**
197    * Sets the VFS.
198    *
199    * @param vfs
200    *          a VFS
201    */
202   public void setVfs(Class<? extends VFS> vfs) {
203     this.vfs = vfs;
204   }
205 
206   /**
207    * Gets the Cache.
208    *
209    * @return a specified Cache
210    */
211   public Cache getCache() {
212     return this.cache;
213   }
214 
215   /**
216    * Sets the Cache.
217    *
218    * @param cache
219    *          a Cache
220    */
221   public void setCache(Cache cache) {
222     this.cache = cache;
223   }
224 
225   /**
226    * Mybatis plugin list.
227    *
228    * @since 1.0.1
229    *
230    * @param plugins
231    *          list of plugins
232    *
233    */
234   public void setPlugins(Interceptor... plugins) {
235     this.plugins = plugins;
236   }
237 
238   /**
239    * Packages to search for type aliases.
240    *
241    * <p>
242    * Since 2.0.1, allow to specify a wildcard such as {@code com.example.*.model}.
243    *
244    * @since 1.0.1
245    *
246    * @param typeAliasesPackage
247    *          package to scan for domain objects
248    *
249    */
250   public void setTypeAliasesPackage(String typeAliasesPackage) {
251     this.typeAliasesPackage = typeAliasesPackage;
252   }
253 
254   /**
255    * Super class which domain objects have to extend to have a type alias created. No effect if there is no package to
256    * scan configured.
257    *
258    * @since 1.1.2
259    *
260    * @param typeAliasesSuperType
261    *          super class for domain objects
262    *
263    */
264   public void setTypeAliasesSuperType(Class<?> typeAliasesSuperType) {
265     this.typeAliasesSuperType = typeAliasesSuperType;
266   }
267 
268   /**
269    * Packages to search for type handlers.
270    *
271    * <p>
272    * Since 2.0.1, allow to specify a wildcard such as {@code com.example.*.typehandler}.
273    *
274    * @since 1.0.1
275    *
276    * @param typeHandlersPackage
277    *          package to scan for type handlers
278    *
279    */
280   public void setTypeHandlersPackage(String typeHandlersPackage) {
281     this.typeHandlersPackage = typeHandlersPackage;
282   }
283 
284   /**
285    * Set type handlers. They must be annotated with {@code MappedTypes} and optionally with {@code MappedJdbcTypes}
286    *
287    * @since 1.0.1
288    *
289    * @param typeHandlers
290    *          Type handler list
291    */
292   public void setTypeHandlers(TypeHandler<?>... typeHandlers) {
293     this.typeHandlers = typeHandlers;
294   }
295 
296   /**
297    * List of type aliases to register. They can be annotated with {@code Alias}
298    *
299    * @since 1.0.1
300    *
301    * @param typeAliases
302    *          Type aliases list
303    */
304   public void setTypeAliases(Class<?>... typeAliases) {
305     this.typeAliases = typeAliases;
306   }
307 
308   /**
309    * If true, a final check is done on Configuration to assure that all mapped statements are fully loaded and there is
310    * no one still pending to resolve includes. Defaults to false.
311    *
312    * @since 1.0.1
313    *
314    * @param failFast
315    *          enable failFast
316    */
317   public void setFailFast(boolean failFast) {
318     this.failFast = failFast;
319   }
320 
321   /**
322    * Set the location of the MyBatis {@code SqlSessionFactory} config file. A typical value is
323    * "WEB-INF/mybatis-configuration.xml".
324    *
325    * @param configLocation
326    *          a location the MyBatis config file
327    */
328   public void setConfigLocation(Resource configLocation) {
329     this.configLocation = configLocation;
330   }
331 
332   /**
333    * Set a customized MyBatis configuration.
334    *
335    * @param configuration
336    *          MyBatis configuration
337    * @since 1.3.0
338    */
339   public void setConfiguration(Configuration configuration) {
340     this.configuration = configuration;
341   }
342 
343   /**
344    * Set locations of MyBatis mapper files that are going to be merged into the {@code SqlSessionFactory} configuration
345    * at runtime.
346    *
347    * This is an alternative to specifying "&lt;sqlmapper&gt;" entries in an MyBatis config file. This property being
348    * based on Spring's resource abstraction also allows for specifying resource patterns here: e.g.
349    * "classpath*:sqlmap/*-mapper.xml".
350    *
351    * @param mapperLocations
352    *          location of MyBatis mapper files
353    */
354   public void setMapperLocations(Resource... mapperLocations) {
355     this.mapperLocations = mapperLocations;
356   }
357 
358   /**
359    * Set optional properties to be passed into the SqlSession configuration, as alternative to a
360    * {@code &lt;properties&gt;} tag in the configuration xml file. This will be used to resolve placeholders in the
361    * config file.
362    *
363    * @param sqlSessionFactoryProperties
364    *          optional properties for the SqlSessionFactory
365    */
366   public void setConfigurationProperties(Properties sqlSessionFactoryProperties) {
367     this.configurationProperties = sqlSessionFactoryProperties;
368   }
369 
370   /**
371    * Set the JDBC {@code DataSource} that this instance should manage transactions for. The {@code DataSource} should
372    * match the one used by the {@code SqlSessionFactory}: for example, you could specify the same JNDI DataSource for
373    * both.
374    *
375    * A transactional JDBC {@code Connection} for this {@code DataSource} will be provided to application code accessing
376    * this {@code DataSource} directly via {@code DataSourceUtils} or {@code DataSourceTransactionManager}.
377    *
378    * The {@code DataSource} specified here should be the target {@code DataSource} to manage transactions for, not a
379    * {@code TransactionAwareDataSourceProxy}. Only data access code may work with
380    * {@code TransactionAwareDataSourceProxy}, while the transaction manager needs to work on the underlying target
381    * {@code DataSource}. If there's nevertheless a {@code TransactionAwareDataSourceProxy} passed in, it will be
382    * unwrapped to extract its target {@code DataSource}.
383    *
384    * @param dataSource
385    *          a JDBC {@code DataSource}
386    *
387    */
388   public void setDataSource(DataSource dataSource) {
389     if (dataSource instanceof TransactionAwareDataSourceProxy) {
390       // If we got a TransactionAwareDataSourceProxy, we need to perform
391       // transactions for its underlying target DataSource, else data
392       // access code won't see properly exposed transactions (i.e.
393       // transactions for the target DataSource).
394       this.dataSource = ((TransactionAwareDataSourceProxy) dataSource).getTargetDataSource();
395     } else {
396       this.dataSource = dataSource;
397     }
398   }
399 
400   /**
401    * Sets the {@code SqlSessionFactoryBuilder} to use when creating the {@code SqlSessionFactory}.
402    *
403    * This is mainly meant for testing so that mock SqlSessionFactory classes can be injected. By default,
404    * {@code SqlSessionFactoryBuilder} creates {@code DefaultSqlSessionFactory} instances.
405    *
406    * @param sqlSessionFactoryBuilder
407    *          a SqlSessionFactoryBuilder
408    *
409    */
410   public void setSqlSessionFactoryBuilder(SqlSessionFactoryBuilder sqlSessionFactoryBuilder) {
411     this.sqlSessionFactoryBuilder = sqlSessionFactoryBuilder;
412   }
413 
414   /**
415    * Set the MyBatis TransactionFactory to use. Default is {@code SpringManagedTransactionFactory}
416    *
417    * The default {@code SpringManagedTransactionFactory} should be appropriate for all cases: be it Spring transaction
418    * management, EJB CMT or plain JTA. If there is no active transaction, SqlSession operations will execute SQL
419    * statements non-transactionally.
420    *
421    * <b>It is strongly recommended to use the default {@code TransactionFactory}.</b> If not used, any attempt at
422    * getting an SqlSession through Spring's MyBatis framework will throw an exception if a transaction is active.
423    *
424    * @see SpringManagedTransactionFactory
425    * @param transactionFactory
426    *          the MyBatis TransactionFactory
427    */
428   public void setTransactionFactory(TransactionFactory transactionFactory) {
429     this.transactionFactory = transactionFactory;
430   }
431 
432   /**
433    * <b>NOTE:</b> This class <em>overrides</em> any {@code Environment} you have set in the MyBatis config file. This is
434    * used only as a placeholder name. The default value is {@code SqlSessionFactoryBean.class.getSimpleName()}.
435    *
436    * @param environment
437    *          the environment name
438    */
439   public void setEnvironment(String environment) {
440     this.environment = environment;
441   }
442 
443   /**
444    * Set scripting language drivers.
445    *
446    * @param scriptingLanguageDrivers
447    *          scripting language drivers
448    * @since 2.0.2
449    */
450   public void setScriptingLanguageDrivers(LanguageDriver... scriptingLanguageDrivers) {
451     this.scriptingLanguageDrivers = scriptingLanguageDrivers;
452   }
453 
454   /**
455    * Set a default scripting language driver class.
456    *
457    * @param defaultScriptingLanguageDriver
458    *          A default scripting language driver class
459    * @since 2.0.2
460    */
461   public void setDefaultScriptingLanguageDriver(Class<? extends LanguageDriver> defaultScriptingLanguageDriver) {
462     this.defaultScriptingLanguageDriver = defaultScriptingLanguageDriver;
463   }
464 
465   /**
466    * {@inheritDoc}
467    */
468   @Override
469   public void afterPropertiesSet() throws Exception {
470     notNull(dataSource, "Property 'dataSource' is required");
471     notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
472     state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
473         "Property 'configuration' and 'configLocation' can not specified with together");
474 
475     this.sqlSessionFactory = buildSqlSessionFactory();
476   }
477 
478   /**
479    * Build a {@code SqlSessionFactory} instance.
480    *
481    * The default implementation uses the standard MyBatis {@code XMLConfigBuilder} API to build a
482    * {@code SqlSessionFactory} instance based on a Reader. Since 1.3.0, it can be specified a {@link Configuration}
483    * instance directly(without config file).
484    *
485    * @return SqlSessionFactory
486    * @throws Exception
487    *           if configuration is failed
488    */
489   protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
490 
491     final Configuration targetConfiguration;
492 
493     XMLConfigBuilder xmlConfigBuilder = null;
494     if (this.configuration != null) {
495       targetConfiguration = this.configuration;
496       if (targetConfiguration.getVariables() == null) {
497         targetConfiguration.setVariables(this.configurationProperties);
498       } else if (this.configurationProperties != null) {
499         targetConfiguration.getVariables().putAll(this.configurationProperties);
500       }
501     } else if (this.configLocation != null) {
502       xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
503       targetConfiguration = xmlConfigBuilder.getConfiguration();
504     } else {
505       LOGGER.debug(
506           () -> "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
507       targetConfiguration = new Configuration();
508       Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables);
509     }
510 
511     Optional.ofNullable(this.objectFactory).ifPresent(targetConfiguration::setObjectFactory);
512     Optional.ofNullable(this.objectWrapperFactory).ifPresent(targetConfiguration::setObjectWrapperFactory);
513     Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl);
514 
515     if (hasLength(this.typeAliasesPackage)) {
516       scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType).stream()
517           .filter(clazz -> !clazz.isAnonymousClass()).filter(clazz -> !clazz.isInterface())
518           .filter(clazz -> !clazz.isMemberClass()).forEach(targetConfiguration.getTypeAliasRegistry()::registerAlias);
519     }
520 
521     if (!isEmpty(this.typeAliases)) {
522       Stream.of(this.typeAliases).forEach(typeAlias -> {
523         targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias);
524         LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'");
525       });
526     }
527 
528     if (!isEmpty(this.plugins)) {
529       Stream.of(this.plugins).forEach(plugin -> {
530         targetConfiguration.addInterceptor(plugin);
531         LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");
532       });
533     }
534 
535     if (hasLength(this.typeHandlersPackage)) {
536       scanClasses(this.typeHandlersPackage, TypeHandler.class).stream().filter(clazz -> !clazz.isAnonymousClass())
537           .filter(clazz -> !clazz.isInterface()).filter(clazz -> !Modifier.isAbstract(clazz.getModifiers()))
538           .forEach(targetConfiguration.getTypeHandlerRegistry()::register);
539     }
540 
541     if (!isEmpty(this.typeHandlers)) {
542       Stream.of(this.typeHandlers).forEach(typeHandler -> {
543         targetConfiguration.getTypeHandlerRegistry().register(typeHandler);
544         LOGGER.debug(() -> "Registered type handler: '" + typeHandler + "'");
545       });
546     }
547 
548     if (!isEmpty(this.scriptingLanguageDrivers)) {
549       Stream.of(this.scriptingLanguageDrivers).forEach(languageDriver -> {
550         targetConfiguration.getLanguageRegistry().register(languageDriver);
551         LOGGER.debug(() -> "Registered scripting language driver: '" + languageDriver + "'");
552       });
553     }
554     Optional.ofNullable(this.defaultScriptingLanguageDriver)
555         .ifPresent(targetConfiguration::setDefaultScriptingLanguage);
556 
557     if (this.databaseIdProvider != null) {// fix #64 set databaseId before parse mapper xmls
558       try {
559         targetConfiguration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
560       } catch (SQLException e) {
561         throw new NestedIOException("Failed getting a databaseId", e);
562       }
563     }
564 
565     Optional.ofNullable(this.cache).ifPresent(targetConfiguration::addCache);
566 
567     if (xmlConfigBuilder != null) {
568       try {
569         xmlConfigBuilder.parse();
570         LOGGER.debug(() -> "Parsed configuration file: '" + this.configLocation + "'");
571       } catch (Exception ex) {
572         throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
573       } finally {
574         ErrorContext.instance().reset();
575       }
576     }
577 
578     targetConfiguration.setEnvironment(new Environment(this.environment,
579         this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,
580         this.dataSource));
581 
582     if (this.mapperLocations != null) {
583       if (this.mapperLocations.length == 0) {
584         LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found.");
585       } else {
586         for (Resource mapperLocation : this.mapperLocations) {
587           if (mapperLocation == null) {
588             continue;
589           }
590           try {
591             XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
592                 targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
593             xmlMapperBuilder.parse();
594           } catch (Exception e) {
595             throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
596           } finally {
597             ErrorContext.instance().reset();
598           }
599           LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");
600         }
601       }
602     } else {
603       LOGGER.debug(() -> "Property 'mapperLocations' was not specified.");
604     }
605 
606     return this.sqlSessionFactoryBuilder.build(targetConfiguration);
607   }
608 
609   /**
610    * {@inheritDoc}
611    */
612   @Override
613   public SqlSessionFactory getObject() throws Exception {
614     if (this.sqlSessionFactory == null) {
615       afterPropertiesSet();
616     }
617 
618     return this.sqlSessionFactory;
619   }
620 
621   /**
622    * {@inheritDoc}
623    */
624   @Override
625   public Class<? extends SqlSessionFactory> getObjectType() {
626     return this.sqlSessionFactory == null ? SqlSessionFactory.class : this.sqlSessionFactory.getClass();
627   }
628 
629   /**
630    * {@inheritDoc}
631    */
632   @Override
633   public boolean isSingleton() {
634     return true;
635   }
636 
637   /**
638    * {@inheritDoc}
639    */
640   @Override
641   public void onApplicationEvent(ApplicationEvent event) {
642     if (failFast && event instanceof ContextRefreshedEvent) {
643       // fail-fast -> check all statements are completed
644       this.sqlSessionFactory.getConfiguration().getMappedStatementNames();
645     }
646   }
647 
648   private Set<Class<?>> scanClasses(String packagePatterns, Class<?> assignableType) throws IOException {
649     Set<Class<?>> classes = new HashSet<>();
650     String[] packagePatternArray = tokenizeToStringArray(packagePatterns,
651         ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
652     for (String packagePattern : packagePatternArray) {
653       Resource[] resources = RESOURCE_PATTERN_RESOLVER.getResources(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
654           + ClassUtils.convertClassNameToResourcePath(packagePattern) + "/**/*.class");
655       for (Resource resource : resources) {
656         try {
657           ClassMetadata classMetadata = METADATA_READER_FACTORY.getMetadataReader(resource).getClassMetadata();
658           Class<?> clazz = Resources.classForName(classMetadata.getClassName());
659           if (assignableType == null || assignableType.isAssignableFrom(clazz)) {
660             classes.add(clazz);
661           }
662         } catch (Throwable e) {
663           LOGGER.warn(() -> "Cannot load the '" + resource + "'. Cause by " + e.toString());
664         }
665       }
666     }
667     return classes;
668   }
669 
670 }