目录
基础篇
一.SpringBoot的自动配置原理是什么?
二.Spring Boot 中如何解决跨域问题 ?
源码篇
四.源码剖析-自动配置
Spring Boot到底是如何进行自动配置的,都把哪些组件进行了自动配置?
@SpringBootApplication
@SpringBootConfiguration
@EnableAutoConfiguration
@AutoConfigurationPackage
六.在SpringBoot中Mybatis自动配置源码分析
主要是Spring Boot的启动类上的核心注解SpringBootApplication注解主配置类,有了这个主配置类启动时就会为SpringBoot开启一个@EnableAutoConfiguration注解自动配置功能。
有了这个EnableAutoConfiguration的话就会:
1. 从配置文件META_INF/Spring.factories加载可能用到的自动配置类
2. 去重,并将exclude和excludeName属性携带的类排除
3. 过滤,将满足条件(@Conditional)的自动配置类返回
跨域可以在前端通过 JSONP 来解决,但是 JSONP 只可以发送 GET 请求,无法发送其他类型的请求,在 RESTful 风格的应用中,就显得非常鸡肋,因此我们推荐在后端通过 (CORS,Cross-origin resource sharing) 来解决跨域问题。这种解决方案并非 Spring Boot 特有的,在传统的SSM 框架中,就可以通过 CORS 来解决跨域问题,只不过之前我们是在 XML 文件中配置 CORS ,现在可以通过实现WebMvcConfigurer接口然后重写addCorsMappings方法解决跨域问题。
@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOrigins("*") .allowCredentials(true) .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .maxAge(3600); } } |
1.SpringBoot项目打包时能打成 jar 与 war包,对比两种打包方式:
jar更加简单方便,使用 java -jar xx.jar 就可以启动。所以打成 jar 包的最多。
而 war包可以部署到tomcat的 webapps 中,随Tomcat的启动而启动。具体使用哪种方
式,应视应用场景而定。
2、打jar包时不会把src/main/webapp 下的内容打到jar包里 (你认为的打到jar包里面,路径是不行的会报404)打war包时会把src/main/webapp 下的内容打到war包里
3.打成什么文件包进行部署与项目业务有关,就像提供 rest 服务的项目需要打包成 jar文件,用命令运行很方便。。。而有大量css、js、html,且需要经常改动的项目,打成 war 包去运行比较方便,因为改动静态资源可以直接覆盖,很快看到改动后的效果,这是 jar 包不能比的(举个‘栗’子:项目打成 jar 包运行,一段时间后,前端要对其中某几个页面样式进行改动,使其更美观,那么改动几个css、html后,需要重新打成一个新的 jar 包,上传服务器并运行,这种改动频繁时很不友好,文件大时上传服务器很耗时,那么 war包就能免去这种烦恼,只要覆盖几个css与html即可).
自动配置:根据我们添加的jar包依赖,会自动将一些配置类的bean注册进ioc容器,我们可以需要的地方使用@autowired或者@resource等注解来使用它。
Spring Boot应用的启动入口是@SpringBootApplication注解标注类中的main()方法,
@SpringBootApplication : SpringBoot 应用标注在某个类上说明这个类是 SpringBoot 的主配置类, SpringBoot 就应该运行这个类的 main() 方法启动 SpringBoot 应用。
下面,查看@SpringBootApplication内部源码进行分析 ,核心代码具体如下
@Target({ElementType.TYPE}) //注解的适用范围,Type表示注解可以描述在类、接口、注解或枚举中 @Retention(RetentionPolicy.RUNTIME) //表示注解的生命周期,Runtime运行时 @Documented //表示注解可以记录在javadoc中 @Inherited //表示可以被子类继承该注解 @SpringBootConfiguration // 标明该类为配置类 @EnableAutoConfiguration // 启动自动配置功能 @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) public @interface SpringBootApplication { // 根据class来排除特定的类,使其不能加入spring容器,传入参数value类型是class类型。 @AliasFor(annotation = EnableAutoConfiguration.class) Class>[] exclude() default {}; // 根据classname 来排除特定的类,使其不能加入spring容器,传入参数value类型是class的全 类名字符串数组。 @AliasFor(annotation = EnableAutoConfiguration.class) String[] excludeName() default {}; // 指定扫描包,参数是包名的字符串数组。 @AliasFor(annotation = ComponentScan.class, attribute = "basePackages") String[] scanBasePackages() default {}; // 扫描特定的包,参数类似是Class类型数组。 @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses") Class>[] scanBasePackageClasses() default {}; } |
从上述源码可以看出,@SpringBootApplication注解是一个组合注解,前面 4 个是注解的元数据信息, 我们主要看后面 3 个注解:@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan三个核心注解,关于这三个核心注解的相关说明具体如下
@SpringBootConfiguration : SpringBoot 的配置类,标注在某个类上,表示这是一个 SpringBoot的配置类。
查看@SpringBootConfiguration注解源码,核心代码具体如下。
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Configuration // 配置类的作用等同于配置文件,配置类也是容器中的一个对象 public @interface SpringBootConfiguration { } |
从上述源码可以看出,@SpringBootConfiguration注解内部有一个核心注解@Configuration,该注解是Spring框架提供的,表示当前类为一个配置类(XML配置文件的注解表现形式),并可以被组件扫描器扫描。由此可见,@SpringBootConfiguration注解的作用与@Configuration注解相同,都是标识一个可以被组件扫描器扫描的配置类,只不过@SpringBootConfiguration是被Spring Boot进行了重新封装命名而已。
package org.springframework.boot.autoconfigure; // 自动配置包 @AutoConfigurationPackage // Spring的底层注解@Import,给容器中导入一个组件; // 导入的组件是AutoConfigurationPackages.Registrar.class @Import(AutoConfigurationImportSelector.class) // 告诉SpringBoot开启自动配置功能,这样自动配置才能生效。 public @interface EnableAutoConfiguration { String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration"; // 返回不会被导入到 Spring 容器中的类 Class>[] exclude() default {}; // 返回不会被导入到 Spring 容器中的类名 String[] excludeName() default {}; } |
Spring 中有很多以 Enable 开头的注解,其作用就是借助 @Import 来收集并注册特定场景相关的Bean ,并加载到 IOC 容器。
@EnableAutoConfiguration就是借助@Import来收集所有符合自动配置条件的bean定义,并加载到IoC容器。
package org.springframework.boot.autoconfigure; @Import(AutoConfigurationPackages.Registrar.class) // 导入Registrar中注册的组件 public @interface AutoConfigurationPackage { } |
@AutoConfigurationPackage :自动配置包,它也是一个组合注解,其中最重要的注解是
@Import(AutoConfigurationPackages.Registrar.class) ,它是 Spring 框架的底层注解,它的作
用就是给容器中导入某个组件类,例如
@Import(AutoConfigurationPackages.Registrar.class) ,它就是将 Registrar 这个组件类导入
到容器中,可查看 Registrar 类中 registerBeanDefinitions 方法:
@Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { // 将注解标注的元信息传入,获取到相应的包名 register(registry, new PackageImport(metadata).getPackageName()); } |
我们对 new PackageImport(metadata).getPackageName() 进行检索,看看其结果是什么?
再看register方法
public static void register(BeanDefinitionRegistry registry, String... packageNames) { // 这里参数 packageNames 缺省情况下就是一个字符串,是使用了注解 // @SpringBootApplication 的 Spring Boot 应用程序入口类所在的包 if (registry.containsBeanDefinition(BEAN)) { // 如果该bean已经注册,则将要注册包名称添加进去 BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN); ConstructorArgumentValues constructorArguments = beanDefinition .getConstructorArgumentValues(); constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames)); } else { //如果该bean尚未注册,则注册该bean,参数中提供的包名称会被设置到bean定义中去 GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); beanDefinition.setBeanClass(BasePackages.class); beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0,packageNames); beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); registry.registerBeanDefinition(BEAN, beanDefinition); } } |
AutoConfigurationPackages.Registrar这个类就干一个事,注册一个 Bean ,这个 Bean 就是
org.springframework.boot.autoconfigure.AutoConfigurationPackages.BasePackages ,它有
一个参数,这个参数是使用了 @AutoConfigurationPackage 这个注解的类所在的包路径,保存自动配置类以供之后的使用,比如给 JPA entity 扫描器用来扫描开发人员通过注解 @Entity 定义的 entity类。
@Import(AutoConfigurationImportSelector.class)
@Import({AutoConfigurationImportSelector.class}) :将
AutoConfigurationImportSelector 这个类导入到 Spring 容器中,
AutoConfigurationImportSelector 可以帮助 Springboot 应用将所有符合条件的 @Configuration
配置都加载到当前 SpringBoot 创建并使用的 IOC 容器( ApplicationContext )中。
可以看到 AutoConfigurationImportSelector 重点是实现了 DeferredImportSelector 接口和各种Aware 接口,然后 DeferredImportSelector 接口又继承了 ImportSelector 接口。
其不光实现了 ImportSelector 接口,还实现了很多其它的 Aware 接口,分别表示在某个时机会被回调。
确定自动配置实现逻辑的入口方法:
跟自动配置逻辑相关的入口方法在 DeferredImportSelectorGrouping 类的 getImports 方法处,因此我们就从 DeferredImportSelectorGrouping 类的 getImports 方法来开始分析SpringBoot的自动配置源码好了。
先看一下 getImports 方法代码:
// ConfigurationClassParser.java public Iterable // 遍历DeferredImportSelectorHolder对象集合deferredImports,deferredImports集合 装了各种ImportSelector,当然这里装的是AutoConfigurationImportSelector for (DeferredImportSelectorHolder deferredImport : this.deferredImports) { // 【1】,利用AutoConfigurationGroup的process方法来处理自动配置的相关逻辑,决定 导入哪些配置类(这个是我们分析的重点,自动配置的逻辑全在这了) this.group.process(deferredImport.getConfigurationClass().getMetadata(), deferredImport.getImportSelector()); } // 【2】,经过上面的处理后,然后再进行选择导入哪些配置类 return this.group.selectImports(); } |
标 【1】 处的的代码是我们分析的重中之重,自动配置的相关的绝大部分逻辑全在这里了。那么this.group.process(deferredImport.getConfigurationClass().getMetadata(),
deferredImport.getImportSelector()) ;主要做的事情就是在 this.group 即AutoConfigurationGroup 对象的 process 方法中,传入的 AutoConfigurationImportSelector
对象来选择一些符合条件的自动配置类,过滤掉一些不符合条件的自动配置类,就是这么个事情。
注: AutoConfigurationGroup:是AutoConfigurationImportSelector的内部类,主要用来处理自动配 置相关的逻辑,拥有process和selectImports方法,然后拥有entries和 autoConfigurationEntries集合属性,这两个集合分别存储被处理后的符合条件的自动配置类,我们知道 这些就足够了; AutoConfigurationImportSelector:承担自动配置的绝大部分逻辑,负责选择一些符合条件的自动配 置类; metadata:标注在SpringBoot启动类上的@SpringBootApplication注解元数据 标【2】的this.group.selectImports的方法主要是针对前面的process方法处理后的自动配置类再进一 步有选择的选择导 |
再进入到AutoConfigurationImportSelector$AutoConfigurationGroup的pross方法:
通过图中我们可以看到,跟自动配置逻辑相关的入口方法在process方法中
分析自动配置的主要逻辑
SpringBoot项目的mian函数
@SpringBootApplication //来标注一个主程序类,说明这是一个Spring Boot应用 public class MyTestMVCApplication { public static void main(String[] args) { SpringApplication.run(MyTestMVCApplication.class, args); } } |
点进run方法
public static ConfigurableApplicationContext run(Class> primarySource, String... args) { // 调用重载方法 return run(new Class>[] { primarySource }, args); } public static ConfigurableApplicationContext run(Class>[] primarySources, String[] args) { // 两件事:1.初始化SpringApplication 2.执行run方法 return new SpringApplication(primarySources).run(args); } |
SpringApplication() 构造方法
继续查看源码, SpringApplication 实例化过程,首先是进入带参数的构造方法,最终回来到两个参数的构造方法。
public SpringApplication(Class>... primarySources) { this(null, primarySources); } @SuppressWarnings({"unchecked", "rawtypes"}) public SpringApplication(ResourceLoader resourceLoader, Class>... primarySources) { //设置资源加载器为null this.resourceLoader = resourceLoader; //断言加载资源类不能为null Assert.notNull(primarySources, "PrimarySources must not be null"); //将primarySources数组转换为List,最后放到LinkedHashSet集合中 this.primarySources = new LinkedHashSet<> (Arrays.asList(primarySources)); //【1.1 推断应用类型,后面会根据类型初始化对应的环境。常用的一般都是servlet环境 】 this.webApplicationType = WebApplicationType.deduceFromClasspath(); //【1.2 初始化classpath下 META-INF/spring.factories中已配置的 ApplicationContextInitializer 】 setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); //【1.3 初始化classpath下所有已配置的 ApplicationListener 】 setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); //【1.4 根据调用栈,推断出 main 方法的类名 】 this.mainApplicationClass = deduceMainApplicationClass(); } |
deduceWebApplicationType();
private static final String[] WEB_ENVIRONMENT_CLASSES = {"javax.servlet.Servlet",
"org.springframework.web.context.ConfigurableWebApplicationContext"}; private static final String REACTIVE_WEB_ENVIRONMENT_CLASS = "org.springframework." + "web.reactive.DispatcherHandler"; private static final String MVC_WEB_ENVIRONMENT_CLASS = "org.springframework." + "web.servlet.DispatcherServlet"; private static final String JERSEY_WEB_ENVIRONMENT_CLASS = "org.glassfish.jersey.server.ResourceConfig"; /** * 判断 应用的类型 * NONE: 应用程序不是web应用,也不应该用web服务器去启动 * SERVLET: 应用程序应作为基于servlet的web应用程序运行,并应启动嵌入式servlet web(tomcat)服务器。 * REACTIVE: 应用程序应作为 reactive web应用程序运行,并应启动嵌入式 reactive web服 务器。 * @return */ private WebApplicationType deduceWebApplicationType() { //classpath下必须存在org.springframework.web.reactive.DispatcherHandler if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null) && !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null) && !ClassUtils.isPresent(JERSEY_WEB_ENVIRONMENT_CLASS, null)) { return WebApplicationType.REACTIVE; } for (String className : WEB_ENVIRONMENT_CLASSES) { if (!ClassUtils.isPresent(className, null)) { return WebApplicationType.NONE; } } //classpath环境下存在javax.servlet.Servlet或者 org.springframework.web.context.ConfigurableWebApplicationContext return WebApplicationType.SERVLET; } |
返回类型是WebApplicationType的枚举类型, WebApplicationType 有三个枚举,三个枚举的解释如其中注释
具体的判断逻辑如下:
WebApplicationType.REACTIVE classpath下存在
org.springframework.web.reactive.DispatcherHandler
WebApplicationType.SERVLET classpath下存在javax.servlet.Servlet或者
org.springframework.web.context.ConfigurableWebApplicationContext
WebApplicationType.NONE 不满足以上条件。
setInitializers((Collection)
getSpringFactoriesInstances(ApplicationContextInitializer.class));
初始化classpath下 META-INF/spring.factories中已配置的ApplicationContextInitializer
private return getSpringFactoriesInstances(type, new Class>[]{}); } /** * 通过指定的classloader 从META-INF/spring.factories获取指定的Spring的工厂实例 * @param type * @param parameterTypes * @param args * @param * @return */ private Class>[] parameterTypes, Object... args) { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); // Use names and ensure unique to protect against duplicates //通过指定的classLoader从 META-INF/spring.factories 的资源文件中, //读取 key 为 type.getName() 的 value Set (SpringFactoriesLoader.loadFactoryNames(type, classLoader)); //创建Spring工厂实例 List classLoader, args, names); //对Spring工厂实例排序(org.springframework.core.annotation.Order注解指定的顺序) AnnotationAwareOrderComparator.sort(instances); return instances; } |
看看 getSpringFactoriesInstances 都干了什么,看源码,有一个方法很重要 loadFactoryNames()这个方法很重要,这个方法是spring-core中提供的从META-INF/spring.factories中获取指定的类(key)的同一入口方法。在这里,获取的是key为 org.springframework.context.ApplicationContextInitializer 的类。
debug看看都获取到了哪些
上面说了,是从classpath下 META-INF/spring.factories中获取,我们验证一下:
上图所示的两个工程中找到了debug中看到的结果。
ApplicationContextInitializer 是Spring框架的类, 这个类的主要目的就是在
ConfigurableApplicationContext 调用refresh()方法之前,回调这个类的initialize方法。
通过 ConfigurableApplicationContext 的实例获取容器的环境Environment,从而实现对配置文件的修改完善等工作。
etListeners((Collection)
getSpringFactoriesInstances(ApplicationListener.class));
初始化classpath下 META-INF/spring.factories中已配置的 ApplicationListener。
ApplicationListener 的加载过程和上面的 ApplicationContextInitializer 类的加载过程是一样的。
不多说了,至于 ApplicationListener 是spring的事件监听器,典型的观察者模式,通过
ApplicationEvent 类和 ApplicationListener 接口,可以实现对spring容器全生命周期的监听,当然也可以自定义监听事件
总结
关于 SpringApplication 类的构造过程,到这里我们就梳理完了。纵观 SpringApplication 类的实例化过程,我们可以看到,合理的利用该类,我们能在spring容器创建之前做一些预备工作,和定制化的需求。
比如,自定义SpringBoot的Banner,比如自定义事件监听器,再比如在容器refresh之前通过自定义ApplicationContextInitializer 修改配置一些配置或者获取指定的bean都是可以的
1、springboot项目最核心的就是自动加载配置,该功能则依赖的是一个注解
@SpringBootApplication中的@EnableAutoConfiguration。
2、EnableAutoConfiguration主要是通过AutoConfigurationImportSelector类来加载
以mybatis为例,*selector通过反射加载spring.factories中指定的java类,也就是加载
MybatisAutoConfiguration类(该类有Configuration注解,属于配置类。
/** * {@link EnableAutoConfiguration Auto-Configuration} for Mybatis. Contributes a 重点:SqlSessionFactory 和 SqlSessionTemplate 两个类 * {@link SqlSessionFactory} and a {@link SqlSessionTemplate}. * * If {@link org.mybatis.spring.annotation.MapperScan} is used, or a * configuration file is specified as a property, those will be considered, * otherwise this auto-configuration will attempt to register mappers based on * the interface definitions in or under the root auto-configuration package. * * @author Eddú Meléndez * @author Josh Long * @author Kazuki Shimizu * @author Eduardo Macarrón */ @org.springframework.context.annotation.Configuration @ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class }) @ConditionalOnBean(DataSource.class) @EnableConfigurationProperties(MybatisProperties.class) @AutoConfigureAfter(DataSourceAutoConfiguration.class) public class MybatisAutoConfiguration { private static final Logger logger = LoggerFactory.getLogger(MybatisAutoConfiguration.class); // //与mybatis配置文件对应 private final MybatisProperties properties; private final Interceptor[] interceptors; private final ResourceLoader resourceLoader; private final DatabaseIdProvider databaseIdProvider; private final List public MybatisAutoConfiguration(MybatisProperties properties, ObjectProvider interceptorsProvider, ResourceLoader resourceLoader, ObjectProvider databaseIdProvider,
ObjectProvider configurationCustomizersProvider) { this.properties = properties; this.interceptors = interceptorsProvider.getIfAvailable(); this.resourceLoader = resourceLoader; this.databaseIdProvider = databaseIdProvider.getIfAvailable(); this.configurationCustomizers = configurationCustomizersProvider.getIfAvailable(); } //postConstruct作用是在创建类的时候先调用, 校验配置文件是否存在 @PostConstruct public void checkConfigFileExists() { if (this.properties.isCheckConfigLocation() && StringUtils.hasText(this.properties.getConfigLocation())) { Resource resource = this.resourceLoader.getResource(this.properties.getConfigLocation()); Assert.state(resource.exists(), "Cannot find config location: " + resource + " (please add config file or check your Mybatis configuration)"); } } //conditionalOnMissingBean作用:在没有类的时候调用,创建sqlsessionFactory sqlsessionfactory最主要的是创建并保存了Configuration类 @Bean @ConditionalOnMissingBean public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { SqlSessionFactoryBean factory = new SqlSessionFactoryBean(); factory.setDataSource(dataSource); factory.setVfs(SpringBootVFS.class); if (StringUtils.hasText(this.properties.getConfigLocation())) { factory.setConfigLocation(this.resourceLoader.getResource(this.properties.g etConfigLocation())); } Configuration configuration = this.properties.getConfiguration(); if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) { configuration = new Configuration(); } if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) { for (ConfigurationCustomizer customizer : this.configurationCustomizers) { customizer.customize(configuration); } } factory.setConfiguration(configuration); if (this.properties.getConfigurationProperties() != null) { factory.setConfigurationProperties(this.properties.getConfigurationProperti es()); } if (!ObjectUtils.isEmpty(this.interceptors)) { factory.setPlugins(this.interceptors); } if (this.databaseIdProvider != null) { factory.setDatabaseIdProvider(this.databaseIdProvider); } if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) { factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage()); } if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) { factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage()); } if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) { factory.setMapperLocations(this.properties.resolveMapperLocations()); } // //获取SqlSessionFactoryBean的getObject()中的对象注入Spring容器,也就是 SqlSessionFactory对象 return factory.getObject(); } @Bean @ConditionalOnMissingBean // 往Spring容器中注入SqlSessionTemplate对象 public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) { ExecutorType executorType = this.properties.getExecutorType(); if (executorType != null) { return new SqlSessionTemplate(sqlSessionFactory, executorType); } else { return new SqlSessionTemplate(sqlSessionFactory); } } |
3、MybatisAutoConfiguration:
①类中有个MybatisProperties类,该类对应的是mybatis的配置文件
②类中有个sqlSessionFactory方法,作用是创建SqlSessionFactory类、Configuration类
(mybatis最主要的类,保存着与mybatis相关的东西)
③SelSessionTemplate,作用是与mapperProoxy代理类有关
sqlSessionFactory主要是通过创建了一个SqlSessionFactoryBean,这个类实现了FactoryBean接口,所以在Spring容器就会注入这个类中定义的getObject方法返回的对象。
看一下getObject()方法做了什么?
@Override public SqlSessionFactory getObject() throws Exception { if (this.sqlSessionFactory == null) { afterPropertiesSet(); } return this.sqlSessionFactory; } @Override public void afterPropertiesSet() throws Exception { notNull(dataSource, "Property 'dataSource' is required"); notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required"); state((configuration == null && configLocation == null) || ! (configuration != null && configLocation != null), "Property 'configuration' and 'configLocation' can not specified with together"); this.sqlSessionFactory = buildSqlSessionFactory(); } |
protected SqlSessionFactory buildSqlSessionFactory() throws Exception { final Configuration targetConfiguration; XMLConfigBuilder xmlConfigBuilder = null; if (this.configuration != null) { targetConfiguration = this.configuration; if (targetConfiguration.getVariables() == null) { targetConfiguration.setVariables(this.configurationProperties); } else if (this.configurationProperties != null) {
targetConfiguration.getVariables().putAll(this.configurationProperties); } } else if (this.configLocation != null) { xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties); targetConfiguration = xmlConfigBuilder.getConfiguration(); } else { LOGGER.debug( () -> "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration"); targetConfiguration = new Configuration(); Optional.ofNullable(this.configurationProperties).ifPresent(targetConfigura tion::setVariables); } Optional.ofNullable(this.objectFactory).ifPresent(targetConfiguration::setO bjectFactory); Optional.ofNullable(this.objectWrapperFactory).ifPresent(targetConfiguratio n::setObjectWrapperFactory); Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl); if (hasLength(this.typeAliasesPackage)) { scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType).stream() .filter(clazz -> !clazz.isAnonymousClass()).filter(clazz -> !clazz.isInterface()) .filter(clazz -> !clazz.isMemberClass()).forEach(targetConfiguration.getTypeAliasRegistry():: registerAlias); } if (!isEmpty(this.typeAliases)) { Stream.of(this.typeAliases).forEach(typeAlias -> { targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias); LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'"); }); } if (!isEmpty(this.plugins)) { Stream.of(this.plugins).forEach(plugin -> { targetConfiguration.addInterceptor(plugin); LOGGER.debug(() -> "Registered plugin: '" + plugin + "'"); }); } if (hasLength(this.typeHandlersPackage)) { scanClasses(this.typeHandlersPackage, TypeHandler.class).stream().filter(clazz -> !clazz.isAnonymousClass()) .filter(clazz -> !clazz.isInterface()).filter(clazz -> !Modifier.isAbstract(clazz.getModifiers())) .forEach(targetConfiguration.getTypeHandlerRegistry()::register); } if (!isEmpty(this.typeHandlers)) { Stream.of(this.typeHandlers).forEach(typeHandler -> { targetConfiguration.getTypeHandlerRegistry().register(typeHandler); LOGGER.debug(() -> "Registered type handler: '" + typeHandler + "'"); }); } targetConfiguration.setDefaultEnumTypeHandler(defaultEnumTypeHandler); if (!isEmpty(this.scriptingLanguageDrivers)) { Stream.of(this.scriptingLanguageDrivers).forEach(languageDriver -> { targetConfiguration.getLanguageRegistry().register(languageDriver); LOGGER.debug(() -> "Registered scripting language driver: '" + languageDriver + "'"); }); } Optional.ofNullable(this.defaultScriptingLanguageDriver) .ifPresent(targetConfiguration::setDefaultScriptingLanguage); if (this.databaseIdProvider != null) {// fix #64 set databaseId before parse mapper xmls try {
targetConfiguration.setDatabaseId(this.databaseIdProvider.getDatabaseId(thi s.dataSource)); } catch (SQLException e) { throw new NestedIOException("Failed getting a databaseId", e); } } Optional.ofNullable(this.cache).ifPresent(targetConfiguration::addCache); if (xmlConfigBuilder != null) { try { xmlConfigBuilder.parse(); LOGGER.debug(() -> "Parsed configuration file: '" + this.configLocation + "'"); } catch (Exception ex) { throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex); } finally { ErrorContext.instance().reset(); } } targetConfiguration.setEnvironment(new Environment(this.environment, this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory, this.dataSource)); if (this.mapperLocations != null) { if (this.mapperLocations.length == 0) { LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found."); } else { for (Resource mapperLocation : this.mapperLocations) { if (mapperLocation == null) { continue; } try { XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments()); //这个方法已经是mybaits的源码,初始化流程 xmlMapperBuilder.parse(); } catch (Exception e) { throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e); } finally { ErrorContext.instance().reset(); } LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'"); } } } else { LOGGER.debug(() -> "Property 'mapperLocations' was not specified."); } //这个方法已经是mybaits的源码,初始化流程 return this.sqlSessionFactoryBuilder.build(targetConfiguration); } |
这个已经很明显了,实际上就是调用了MyBatis的初始化流程
现在已经得到了SqlSessionFactory了,接下来就是如何扫描到相关的Mapper接口了。
这个需要看这个注解
@MapperScan(basePackages = “com.mybatis.mapper”)
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import(MapperScannerRegistrar.class) @Repeatable(MapperScans.class) public @interface MapperScan |
通过@Import的方式会扫描到MapperScannerRegistrar类。
MapperScannerRegistrar实现了ImportBeanDefinitionRegistrar接口,那么在spring实例化之前就会调用到registerBeanDefinitions方法
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware |
@Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { //拿到MapperScan注解,并解析注解中定义的属性封装成AnnotationAttributes对象 AnnotationAttributes mapperScanAttrs = AnnotationAttributes
.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.get Name())); if (mapperScanAttrs != null) { registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry, generateBaseBeanName(importingClassMetadata, 0)); } } void registerBeanDefinitions(AnnotationMetadata annoMeta, 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 basePackages.addAll(
Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasTex t).collect(Collectors.toList())); basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("basePackages")) .filter(StringUtils::hasText) .collect(Collectors.toList())); basePackages.addAll(Arrays.stream(annoAttrs.getClassArray("basePackageClass es")).map(ClassUtils::getPackageName) .collect(Collectors.toList())); if (basePackages.isEmpty()) { basePackages.add(getDefaultBasePackage(annoMeta)); } String lazyInitialization = annoAttrs.getString("lazyInitialization"); if (StringUtils.hasText(lazyInitialization)) { builder.addPropertyValue("lazyInitialization", lazyInitialization); } builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages)); //把类型为MapperScannerConfigurer的注册到spring容器中 registry.registerBeanDefinition(beanName, builder.getBeanDefinition()); } |
MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口,所以接着又会扫
描并调用到postProcessBeanDefinitionRegistry方法。
public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware |
@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)); } public int scan(String... basePackages) { int beanCountAtScanStart = this.registry.getBeanDefinitionCount(); doScan(basePackages); // Register annotation config processors, if necessary. if (this.includeAnnotationConfig) { AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry); } return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart); } @Override public Set //这个方法主要就注册扫描basePackages路径下的mapper接口,然后封装成一个 BeanDefinition后加入到spring容器中 Set if (beanDefinitions.isEmpty()) { LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration."); } else { //这个方法主要会把原BeanDefinition的beanClass类型,修改为MapperFactoryBean processBeanDefinitions(beanDefinitions); } return beanDefinitions; } |
修改了mapper的beanClass类型为MapperFactoryBean
private void processBeanDefinitions(Set beanDefinitions) { GenericBeanDefinition definition; for (BeanDefinitionHolder holder : beanDefinitions) { definition = (GenericBeanDefinition) holder.getBeanDefinition(); String beanClassName = definition.getBeanClassName(); LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName + "' mapperInterface"); // the mapper interface is the original class of the bean // but, the actual class of the bean is MapperFactoryBean definition.getConstructorArgumentValues().addGenericArgumentValue(beanClass Name); // issue #59 //修改beanClass类型 definition.setBeanClass(this.mapperFactoryBeanClass); definition.getPropertyValues().add("addToConfig", this.addToConfig); boolean explicitFactoryUsed = false; if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) { definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName)); explicitFactoryUsed = true; } else if (this.sqlSessionFactory != null) { definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory); explicitFactoryUsed = true; } if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) { if (explicitFactoryUsed) { LOGGER.warn( () -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored."); } definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName)); explicitFactoryUsed = true; } else if (this.sqlSessionTemplate != null) { if (explicitFactoryUsed) { LOGGER.warn( () -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored."); } definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate); explicitFactoryUsed = true; } if (!explicitFactoryUsed) { LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'."); definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); } definition.setLazyInit(lazyInitialization); } } |
上述几步主要是完成通过
@MapperScan(basePackages = “com.mybatis.mapper”)这个定义,扫描指定包下的
mapper接口,然后设置每个mapper接口的beanClass属性为MapperFactoryBean类型并加入
到spring的bean容器中。
MapperFactoryBean实现了FactoryBean接口,所以当spring从待实例化的bean容器中遍历到这个bean并开始执行实例化时返回的对象实际上是getObject方法中返回的对象。
public class MapperFactoryBean FactoryBean |
最后看一下MapperFactoryBean的getObject方法,实际上返回的就是mybatis中通过getMapper拿到的对象,熟悉mybatis源码的就应该清楚,这个就是mybatis通过动态代理生成的mapper接口实现类'
@Override public T getObject() throws Exception { return getSqlSession().getMapper(this.mapperInterface); } |
到此,mapper接口现在也通过动态代理生成了实现类,并且注入到spring的bean容器中了,之后使用者就可以通过@Autowired或者getBean等方式,从spring容器中获取到了。