目录
- 如何引入第三方库
- 第三方库与当前项目Bean重复
- 自定义自动配置类
自动配置类通常位于Spring Boot的自动配置模块中,并且被标记为
@Configuration
类。这些类使用
@Conditional
注解来检查某些条件是否满足,如果满足,则创建和配置相关的bean。这些条件可能包括检查类路径上是否存在特定的类、检查应用程序的属性设置、检查是否存在特定的bean等。
自动配置类还可用于自动配置各种常见的Spring组件和第三方库。这些自动配置类大大简化了应用程序的配置过程,使得开发者可以更加专注于实现业务逻辑,而不是花费大量时间在繁琐的配置上。
如何引入第三方库
看如下示例:
package com.cys.spring.chapter16;import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ConfigurationClassPostProcessor;
import org.springframework.context.annotation.Import;
import org.springframework.context.support.GenericApplicationContext;public class TestAutoConfig {@SuppressWarnings("all")public static void main(String[] args) {GenericApplicationContext context = new GenericApplicationContext();context.registerBean("config", Config.class);context.registerBean(ConfigurationClassPostProcessor.class);context.refresh();for (String name : context.getBeanDefinitionNames()) {System.out.println(name);}System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>");System.out.println(context.getBean(Bean1.class));}@Configuration // 本项目的配置类@Import({AutoConfiguration1.class, AutoConfiguration2.class})static class Config {}@Configuration // 第三方的配置类static class AutoConfiguration1 {@Beanpublic Bean1 bean1() {return new Bean1("第三方");}}static class Bean1 {private String name;public Bean1() {}public Bean1(String name) {this.name = name;}@Overridepublic String toString() {return "Bean1{" +"name='" + name + '\'' +'}';}}@Configuration // 第三方的配置类static class AutoConfiguration2 {@Beanpublic Bean2 bean2() {return new Bean2();}}static class Bean2 {}
}
运行:
config
org.springframework.context.annotation.ConfigurationClassPostProcessor
com.cys.spring.chapter16.TestAutoConfig$AutoConfiguration1
bean1
com.cys.spring.chapter16.TestAutoConfig$AutoConfiguration2
bean2
>>>>>>>>>>>>>>>>>>>>>>>>>
Bean1{name='第三方'}
首先在我们当前应用的配置类Config中,使用@Import({AutoConfiguration1.class, AutoConfiguration2.class})
将模拟出的第三方的配置类AutoConfiguration1
和AutoConfiguration2
导入到了我们自己的应用程序中,接着就可以使用他们俩的配置类中创建的Bean。
接着我们优化一下,不把导入的类名写死在代码,而是配合ImportSelector
,将其写在配置文件中,文件名需要是src/main/resources/META-INF/spring.factories
配置文件内容:
com.cys.spring.chapter16.TestAutoConfig$MyImportSelector=\
com.cys.spring.chapter16.TestAutoConfig.AutoConfiguration1,\
com.cys.spring.chapter16.TestAutoConfig.AutoConfiguration2
然后修改测试类如下:
@Configuration // 本项目的配置类
@Import(MyImportSelector.class)
// @Import({AutoConfiguration1.class, AutoConfiguration2.class})
static class Config {
}/*** 创建一个ImportSelector,返回配置文件的类名*/
static class MyImportSelector implements ImportSelector {@Overridepublic String[] selectImports(AnnotationMetadata importingClassMetadata) {List<String> names = SpringFactoriesLoader.loadFactoryNames(MyImportSelector.class, null);return names.toArray(new String[0]);}
}
这样也是没问题的。
第三方库与当前项目Bean重复
如果第三方库与当前项目Bean重复,默认是引入的第三方的先创建,然后自己程序的再创建,且后创建的可以覆盖前面的。
可以设置为不允许覆盖,就会有Bean重复的报错,设置方法如下:
setAllowBeanDefinitionOverriding(false);
修改后如下:
public static void main(String[] args) {GenericApplicationContext context = new GenericApplicationContext();// 如果有同名的Bean,是否允许覆盖context.getDefaultListableBeanFactory().setAllowBeanDefinitionOverriding(false);context.registerBean("config", Config.class);context.registerBean(ConfigurationClassPostProcessor.class);context.refresh();for (String name : context.getBeanDefinitionNames()) {System.out.println(name);}System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>");System.out.println(context.getBean(Bean1.class));}
运行后报错:
Exception in thread "main" org.springframework.beans.factory.support.BeanDefinitionOverrideException: Invalid bean definition with name 'bean1' defined in com.cys.spring.chapter16.TestAutoConfig$Config: Cannot register bean definition [Root bean: class [null]; defined in com.cys.spring.chapter16.TestAutoConfig$Config] for bean 'bean1': There is already [Root bean: class [null]; defined in class path resource [com/cys/spring/chapter16/TestAutoConfig$AutoConfiguration1.class]] bound.
意思就是无法注册TestAutoConfig C o n f i g 中的 b e a n 1 的 b e a n d e f i n i t i o n ,因为在 T e s t A u t o C o n f i g Config中的bean1的 bean definition,因为在TestAutoConfig Config中的bean1的beandefinition,因为在TestAutoConfigAutoConfiguration1.class中已经存在。
实际生产中不建议这么做,建议开启覆盖。
那么如果要调整注册 bean definition的顺序呢,让自己程序的先注册,第三方的后注册呢?也可以,但实际生产也不建议这么做。
真要修改的话可以创建ImportSelector时,实现DeferredImportSelector
,达到延迟注册的目的。如下
static class MyImportSelector implements DeferredImportSelector {@Overridepublic String[] selectImports(AnnotationMetadata importingClassMetadata) {List<String> names = SpringFactoriesLoader.loadFactoryNames(MyImportSelector.class, null);return names.toArray(new String[0]);}}
自定义自动配置类
在Springboot中,自动配置通常使用@EnableAutoConfiguration
。源码如下:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//package org.springframework.boot.autoconfigure;import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";Class<?>[] exclude() default {};String[] excludeName() default {};
}
他也是使用了@Import
注解,去找spring.fatories
文件中找AutoConfigurationImportSelector
的key。AutoConfigurationImportSelectory中也有个selectImports
,其源码如下:
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {private static final AutoConfigurationImportSelector.AutoConfigurationEntry EMPTY_ENTRY = new AutoConfigurationImportSelector.AutoConfigurationEntry();private static final String[] NO_IMPORTS = new String[0];private static final Log logger = LogFactory.getLog(AutoConfigurationImportSelector.class);private static final String PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE = "spring.autoconfigure.exclude";private ConfigurableListableBeanFactory beanFactory;private Environment environment;private ClassLoader beanClassLoader;private ResourceLoader resourceLoader;private AutoConfigurationImportSelector.ConfigurationClassFilter configurationClassFilter;public AutoConfigurationImportSelector() {}public String[] selectImports(AnnotationMetadata annotationMetadata) {if (!this.isEnabled(annotationMetadata)) {return NO_IMPORTS;} else {AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());}}
}
主要方法getAutoConfigurationEntry
:
protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {if (!this.isEnabled(annotationMetadata)) {return EMPTY_ENTRY;} else {AnnotationAttributes attributes = this.getAttributes(annotationMetadata);List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);configurations = this.removeDuplicates(configurations);Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);this.checkExcludedClasses(configurations, exclusions);configurations.removeAll(exclusions);configurations = this.getConfigurationClassFilter().filter(configurations);this.fireAutoConfigurationImportEvents(configurations, exclusions);return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);}
}
进入方法getCandidateConfigurations
:
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");return configurations;
}
他也是使用方法SpringFactoriesLoader.loadFactoryNames
找配置类名称,其中key是this.getSpringFactoriesLoaderFactoryClass()
方法返回值,
protected Class<?> getSpringFactoriesLoaderFactoryClass() {return EnableAutoConfiguration.class;
}
进入方法看到,他最后找的类名为EnableAutoConfiguration
的key。
根据这个,我们在spring.factories
中添加这个key:
com.cys.spring.chapter16.TestAutoConfig$MyImportSelector=\
com.cys.spring.chapter16.TestAutoConfig.AutoConfiguration1,\
com.cys.spring.chapter16.TestAutoConfig.AutoConfiguration2org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.cys.spring.chapter16.TestAutoConfig.AutoConfiguration1,\
com.cys.spring.chapter16.TestAutoConfig.AutoConfiguration2
并在配置类上加上@EnableAutoConfiguration
:
package com.cys.spring.chapter16;import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DeferredImportSelector;
import org.springframework.core.env.SimpleCommandLinePropertySource;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.core.type.AnnotationMetadata;import java.io.IOException;public class TestSelfAutoConfiguration {@SuppressWarnings("all")public static void main(String[] args) throws IOException {AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext();StandardEnvironment env = new StandardEnvironment();env.getPropertySources().addLast(new SimpleCommandLinePropertySource("--spring.datasource.url=jdbc:mysql://localhost:3306/test","--spring.datasource.username=root","--spring.datasource.password=root"));context.setEnvironment(env);context.registerBean("config", Config.class);context.refresh();for (String name : context.getBeanDefinitionNames()) {String resourceDescription = context.getBeanDefinition(name).getResourceDescription();if (resourceDescription != null)System.out.println(name + " 来源:" + resourceDescription);}context.close();}@Configuration // 本项目的配置类
// @Import(MyImportSelector.class)
// @Import(AutoConfigurationImportSelector.class)@EnableAutoConfigurationstatic class Config {@Beanpublic TomcatServletWebServerFactory tomcatServletWebServerFactory() {return new TomcatServletWebServerFactory();}}static class MyImportSelector implements DeferredImportSelector {@Overridepublic String[] selectImports(AnnotationMetadata importingClassMetadata) {return SpringFactoriesLoader.loadFactoryNames(MyImportSelector.class, null).toArray(new String[0]);}}@Configuration // 第三方的配置类static class AutoConfiguration1 {@Beanpublic Bean1 bean1() {return new Bean1();}}@Configuration // 第三方的配置类static class AutoConfiguration2 {@Beanpublic Bean2 bean2() {return new Bean2();}}static class Bean1 {}static class Bean2 {}
}
运行后检查,返现bean1和bean2