一、引言
SpringBoot是基於Spring基礎上而生的一個微服務框架,相比於Spring,強調約定大於配置,具有自動化配置、快速開發,自動部署等優點。那么SpringBoot是怎么來簡化原先Spring那些繁瑣的配置的呢?自動裝配。本篇博客,主要是想簡單闡述一下SpringBoot的自動裝配機制和手寫自定義一個starter。
二、自動裝配原理
2.1 從熟知的啟動類開始
@SpringBootApplication public class Start { public static void main(String[] args) {
// 啟動IOC和tomcat容器 SpringApplication.run(Start.class, args); } }
這個是我們熟悉的SpringBoot的啟動類。自動裝配的核心就是注解@SpringBootApplication。
2.2 注解@SpringBootApplication
@SpringBootApplication是SpringBoot自定義的一個注解。我們先來看這個注解的源碼片段,實際上對自動裝配產生作用的就是兩個:
@SpringBootConfiguration 和 @EnableAutoConfiguration
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) public @interface SpringBootApplication {
}
先簡單分析源碼的前四個注解。這個四個注解屬於Java的元注解,意為修飾注解的注解。
@Target:自定義注解的使用范圍,如類、方法、屬性。比方說@Controller是作用在類上的,如果作用在方法上就會報錯。
@Retention:這個注解有三個屬性:source、class、runtime(最常見也最常用),主要定義自定義注解的生效時間。
(1)source,意味着當Java文件編譯成源文件(.class),這個注解就會被遺棄,只保留在源文件,主要是提供給編譯用的,比方說我們熟悉的Override;
(2)class,意味着實現一些檢查性的操作,如supperessWarning,摒棄一些告警標識;
(3)runtime:意味着運行時生效,最常見和使用。
@Documented:生成javadoc文檔
@Inherited:所修飾的自定義注解可以被子類繼承。如我們剛才看到的Start類,如果有一個類繼承了它,那么默認也會擁有@SpringBootApplication注解的功能。
2.3 @SpringBootConfiguration
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Configuration(proxyBeanMethods = false) public @interface SpringBootConfiguration { /** * Specify whether {@link Bean @Bean} methods should get proxied in order to enforce * bean lifecycle behavior, e.g. to return shared singleton bean instances even in * case of direct {@code @Bean} method calls in user code. This feature requires * method interception, implemented through a runtime-generated CGLIB subclass which * comes with limitations such as the configuration class and its methods not being * allowed to declare {@code final}. * <p> * The default is {@code true}, allowing for 'inter-bean references' within the * configuration class as well as for external calls to this configuration's * {@code @Bean} methods, e.g. from another configuration class. If this is not needed * since each of this particular configuration's {@code @Bean} methods is * self-contained and designed as a plain factory method for container use, switch * this flag to {@code false} in order to avoid CGLIB subclass processing. * <p> * Turning off bean method interception effectively processes {@code @Bean} methods * individually like when declared on non-{@code @Configuration} classes, a.k.a. * "@Bean Lite Mode" (see {@link Bean @Bean's javadoc}). It is therefore behaviorally * equivalent to removing the {@code @Configuration} stereotype. * @return whether to proxy {@code @Bean} methods * @since 2.2 */ @AliasFor(annotation = Configuration.class) boolean proxyBeanMethods() default true; }
這個注解只有一個屬性proxyBeanMethods,而且這個屬性是從Configuration繼承而來的。默認為true,它會采用CGLIB去做代理這個配置類。
拋開上面三個元注解,就只有一個@Configuration。也就是說@SpringBootApplication可以等同於@Configuration注解。
擴展:@Configuration和@Component區別
2.4 @EnableAutoConfiguration
自動裝配主要依賴@EnableAutoConfiguration來實現
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration { String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration"; /** * Exclude specific auto-configuration classes such that they will never be applied. * @return the classes to exclude */ Class<?>[] exclude() default {}; /** * Exclude specific auto-configuration class names such that they will never be * applied. * @return the class names to exclude * @since 1.3.0 */ String[] excludeName() default {}; }
2.4.1 @Import注解解釋
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Import { /** * {@link Configuration @Configuration}, {@link ImportSelector}, * {@link ImportBeanDefinitionRegistrar}, or regular component classes to import. */ Class<?>[] value(); }
由注解的解釋,我們可以看到,@Import作為Spring的注解,主要是想將類注入到IOC中去,主要由以下三種用法:
(1)實現ImportSelector接口selectImport方法。這個方法返回的是類的全路徑的數據,這個數組中的類都會被掃描到IOC容器。
public interface ImportSelector { /** * Select and return the names of which class(es) should be imported based on * the {@link AnnotationMetadata} of the importing @{@link Configuration} class. */ String[] selectImports(AnnotationMetadata importingClassMetadata); }
(2)實現ImportBeanDefinitionRegistrar接口。注意看參數,BeanDefinitionRegistry registry,是通過registry將bean注入到IOC容器。這是一個手工注入bean,自己編碼去實現。
public interface ImportBeanDefinitionRegistrar { /** * Register bean definitions as necessary based on the given annotation metadata of * the importing {@code @Configuration} class. * <p>Note that {@link BeanDefinitionRegistryPostProcessor} types may <em>not</em> be * registered here, due to lifecycle constraints related to {@code @Configuration} * class processing. * <p>The default implementation delegates to * {@link #registerBeanDefinitions(AnnotationMetadata, BeanDefinitionRegistry)}. * @param importingClassMetadata annotation metadata of the importing class * @param registry current bean definition registry * @param importBeanNameGenerator the bean name generator strategy for imported beans: * {@link ConfigurationClassPostProcessor#IMPORT_BEAN_NAME_GENERATOR} by default, or a * user-provided one if {@link ConfigurationClassPostProcessor#setBeanNameGenerator} * has been set. In the latter case, the passed-in strategy will be the same used for * component scanning in the containing application context (otherwise, the default * component-scan naming strategy is {@link AnnotationBeanNameGenerator#INSTANCE}). * @since 5.2 * @see ConfigurationClassPostProcessor#IMPORT_BEAN_NAME_GENERATOR * @see ConfigurationClassPostProcessor#setBeanNameGenerator */ default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) { registerBeanDefinitions(importingClassMetadata, registry); } /** * Register bean definitions as necessary based on the given annotation metadata of * the importing {@code @Configuration} class. * <p>Note that {@link BeanDefinitionRegistryPostProcessor} types may <em>not</em> be * registered here, due to lifecycle constraints related to {@code @Configuration} * class processing. * <p>The default implementation is empty. * @param importingClassMetadata annotation metadata of the importing class * @param registry current bean definition registry */ default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { } }
(3)普通類,直接被Spring掃描到IOC中去。
2.4.2 @AutoConfigurationPackage
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Import(AutoConfigurationPackages.Registrar.class) public @interface AutoConfigurationPackage { }
我們來看:AutoConfigurationPackages.Registrar實現了ImportBeanDefinitionRegistrar就是想手動去注入bean。注冊又是一個什么樣的bean,又要做什么呢?
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports { @Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { register(registry, new PackageImport(metadata).getPackageName()); } @Override public Set<Object> determineImports(AnnotationMetadata metadata) { return Collections.singleton(new PackageImport(metadata)); } }
我們看源碼的registerBeanDefinitions方法,new PackageImport(),這是一個內部類,首先它會先去獲取AutoConfigurationPackage,最終要獲得這個類上的basePackages,當然如果你覆寫了basePackage這個屬性就是你定義,沒有就是我們當前Start類的當前類的路徑,這個包路徑不允許程序所修改,得到這個路徑之后,封裝成一個不可變的集合賦給全局的變量this.packageNames。
private static final class PackageImports { private final List<String> packageNames; PackageImports(AnnotationMetadata metadata) { AnnotationAttributes attributes = AnnotationAttributes .fromMap(metadata.getAnnotationAttributes(AutoConfigurationPackage.class.getName(), false)); List<String> packageNames = new ArrayList<>(); for (String basePackage : attributes.getStringArray("basePackages")) { packageNames.add(basePackage); } for (Class<?> basePackageClass : attributes.getClassArray("basePackageClasses")) { packageNames.add(basePackageClass.getPackage().getName()); } if (packageNames.isEmpty()) { packageNames.add(ClassUtils.getPackageName(metadata.getClassName())); } this.packageNames = Collections.unmodifiableList(packageNames); } List<String> getPackageNames() { return this.packageNames; } @Override public boolean equals(Object obj) { if (obj == null || getClass() != obj.getClass()) { return false; } return this.packageNames.equals(((PackageImports) obj).packageNames); } @Override public int hashCode() { return this.packageNames.hashCode(); } @Override public String toString() { return "Package Imports " + this.packageNames; } }
this.packageNames這個全局變量作用是什么呢?回到registerBeanDefinitions()實現的register,這里的packageNames就是剛才得到的包路徑,這里當然還有我們熟悉和Spring生命周期相關的的BeanDefinition,通過BasePackage類構造器,賦值給全局packages,提供get()方法給別人做查詢使用。
public static void register(BeanDefinitionRegistry registry, String... packageNames) { if (registry.containsBeanDefinition(BEAN)) { BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN); ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues(); constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames)); } else { GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); beanDefinition.setBeanClass(BasePackages.class); beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames); beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); registry.registerBeanDefinition(BEAN, beanDefinition); } }
2.4.3 @Import(AutoConfigurationImportSelector.class)
我們注解上的這個類:AutoConfigurationImportSelector
@Override public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return NO_IMPORTS; } AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata); return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); }
實現的這個方法返回包含類的全路徑的數組。
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return EMPTY_ENTRY; } AnnotationAttributes attributes = getAttributes(annotationMetadata); List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); configurations = removeDuplicates(configurations); Set<String> exclusions = getExclusions(annotationMetadata, attributes); checkExcludedClasses(configurations, exclusions); configurations.removeAll(exclusions); configurations = getConfigurationClassFilter().filter(configurations); fireAutoConfigurationImportEvents(configurations, exclusions); return new AutoConfigurationEntry(configurations, exclusions); }
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), 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; }
loadFactoryName主要是加載Bean的。getSpringFactoriesLoaderFactoryClass()返回地是EnableAutoConfiguration.class對象,getBeanClassLoader獲得類加載器。這里使用到類加載器,主要就是想加載資源,加載什么樣的資源,繼續如下:
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) { String factoryTypeName = factoryType.getName(); return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList()); } private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) { MultiValueMap<String, String> result = cache.get(classLoader); if (result != null) { return result; } try { Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION)); result = new LinkedMultiValueMap<>(); while (urls.hasMoreElements()) { URL url = urls.nextElement(); UrlResource resource = new UrlResource(url); Properties properties = PropertiesLoaderUtils.loadProperties(resource); for (Map.Entry<?, ?> entry : properties.entrySet()) { String factoryTypeName = ((String) entry.getKey()).trim(); for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) { result.add(factoryTypeName, factoryImplementationName.trim()); } } } cache.put(classLoader, result); return result; } catch (IOException ex) { throw new IllegalArgumentException("Unable to load factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex); } }
也就是最終會掃描:"META-INF/spring.factories"這個相對路徑下的資源,我們找到這個資源:

這里面類似key-value的配置,value可以看作是一些具體的實現類,我們重點先關注下:org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,
這些配置的實現類最終都會加載到IOC容器中去。

在這個EnableAutoConfiguration中,就有一個:org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration。這里實現中,通過@Bean就會把我們熟悉的DispacherServlet加載到IOC容器中去。這里就是為什么使用SpringBoot時,就沒讓我們再去配置DispacherServlet了。
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) @Configuration(proxyBeanMethods = false) @ConditionalOnWebApplication(type = Type.SERVLET) @ConditionalOnClass(DispatcherServlet.class) @AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class) public class DispatcherServletAutoConfiguration { /* * The bean name for a DispatcherServlet that will be mapped to the root URL "/" */ public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "dispatcherServlet"; @Configuration(proxyBeanMethods = false) @Conditional(DefaultDispatcherServletCondition.class) @ConditionalOnClass(ServletRegistration.class) @EnableConfigurationProperties(WebMvcProperties.class) protected static class DispatcherServletConfiguration { @Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME) public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) { DispatcherServlet dispatcherServlet = new DispatcherServlet(); dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest()); dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest()); dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound()); dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents()); dispatcherServlet.setEnableLoggingRequestDetails(webMvcProperties.isLogRequestDetails()); return dispatcherServlet; }
當然,你可能問?光是加載到容器就足夠了嗎?因為以前可還需要將Servlet加載到tomcat容器的上下文啊,這樣才能正常的運轉啊?那么,這里SpringBoot是怎么做到的呢?
也是在同一個類,還有一個@Bean。看這個:DispatcherServletRegistrationBean。這個類的父類是:ServletRegistrationBean。可能你會比較熟悉,因為我們可能會利用它去開發和web三大組件相關的內容:Filter、Listener、Servlet。
@Configuration(proxyBeanMethods = false) @Conditional(DispatcherServletRegistrationCondition.class) @ConditionalOnClass(ServletRegistration.class) @EnableConfigurationProperties(WebMvcProperties.class) @Import(DispatcherServletConfiguration.class) protected static class DispatcherServletRegistrationConfiguration { @Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME) @ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME) public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet, WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) { DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet, webMvcProperties.getServlet().getPath()); registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME); registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup()); multipartConfig.ifAvailable(registration::setMultipartConfig); return registration; } }
ServletRegistrationBean繼承於RegistrationBean。RegistrationBean這個類有幾個是是實現類。其中,可以看到ServletListenerResgistrationBean(監聽器)等。

而在ServletRegistrationBean中,有這么一個方法:通過ServletContext的addServlet方法。將servlet添加到tomcat容器中去。這里的this.servlet是上面DispatcherServletRegistrationBean 通過構造器傳入進來的dispacherServlet.
@Override protected ServletRegistration.Dynamic addRegistration(String description, ServletContext servletContext) { String name = getServletName(); return servletContext.addServlet(name, this.servlet); }
問題是:addRegistration這個是怎么觸發的呢?再來看以下RegistrationBean。
public abstract class RegistrationBean implements ServletContextInitializer, Ordered
這個類實現了一個接口:ServletContextInitializer,利用了tomcat的SPI規范,將這個接口的實現類注入到tomcat的上下文中去,在tomcat的上下文,就會去調用onStartup方法。最終就會觸發到addRegistration。
@FunctionalInterface public interface ServletContextInitializer { /** * Configure the given {@link ServletContext} with any servlets, filters, listeners * context-params and attributes necessary for initialization. * @param servletContext the {@code ServletContext} to initialize * @throws ServletException if any call against the given {@code ServletContext} * throws a {@code ServletException} */ void onStartup(ServletContext servletContext) throws ServletException; }
言歸正傳,再回到我們的AutoConfigurationImportSelector的getAutoConfigurationEntry,為了方便,這里再貼一下這個方法的代碼:、
/** * Return the {@link AutoConfigurationEntry} based on the {@link AnnotationMetadata} * of the importing {@link Configuration @Configuration} class. * @param annotationMetadata the annotation metadata of the configuration class * @return the auto-configurations that should be imported */ protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return EMPTY_ENTRY; } AnnotationAttributes attributes = getAttributes(annotationMetadata); List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); configurations = removeDuplicates(configurations); Set<String> exclusions = getExclusions(annotationMetadata, attributes); checkExcludedClasses(configurations, exclusions); configurations.removeAll(exclusions); configurations = getConfigurationClassFilter().filter(configurations); fireAutoConfigurationImportEvents(configurations, exclusions); return new AutoConfigurationEntry(configurations, exclusions); }
當掃描到一堆配置類之后,通過removeDuplicates方法做了一個去重,去重的目的就是因為在classpath下的spring.factories文件內容都要掃描,去除相同的配置。
getExclusions就是排除到定義的不需要自動裝配的類。
checkExcludedClasses則是需要進行自動裝配的類做一個檢查,不合法的需要校驗。
緊接着會加載一個過濾器再進行一遍過濾。這個過濾器就是AutoConfigurationImportFilter。
fireAutoConfigurationImportEvents相當於Springboot提供的一個擴展點,基於事件驅動的,如果程序想參與到這個自動裝配的話,可以去監聽這個事件,監聽到這個事件之后,可以做自己的一些操作。相關AutoConfigurationImportListener
最后,將所有需要裝配的類封裝到一個對象上:AutoConfigurationEntry。最終轉化作為selectImport的返回值。
三、自定義寫一個starter
3.1 自動裝配的使用場景
這個自動裝配除了幫我們自動裝配DispatcherServlet,幫我們去加載類似tomcat、redis等,那我們還可以用來做什么?比方說實際開發中的一些公共的jar包(公共組件)等;
舉個例子,比方說我們去開發一個Filter,要把這個Filter加載到應用程序中去(RegistrationBean );或者想使用一個服務,@Autowired 去注入jar里面的一個bean,但是你直接注入是不行的,可能你需要先使用<bean/>標簽或者@Bean的注解。因為我要把其他這個jar包的bean通過bean標簽或者注解寫入進來,就對我的代碼有了侵入,因為假如這個jar包的名字改了,或者這么bean我不用了(就像剛才的filter),我除了剔除jar包,我還要修改很多依賴的代碼。也就是這樣公共包會顯得比較重。
有了自動裝配,比方說Filter,可以把filter的裝配自閉在一個jar包里面,別人不想使用了,剔除相應jar就行。就像想使用@Autowired去注入別人bean,不需要自己寫<bean/>或者@Bean。直接@Autowired去使用就好。
3.2 簡單案例--自定義starter
先提及到一個開發規范,對於包命名問題。
- autoconfiguration:自動裝配的核心代碼。
- starter:管理Jar。如果是Spring官方的,spring-boot-starter-xxx, 如果是自己定義,命名xxx-spring-boot-starter。
3.2.1 使用spring.factories
首先,我們做一個簡單獲取當前時間的工具類和一個filter(filter就簡單打印一個訪問時間),放在util-spring-boot-autoconfigure
工程目錄大概如下,boot-application依賴starter,starter依賴autoconfigure:

代碼簡單如下:
public class DateUtil { public String getNowTime() { LocalDateTime localDate = LocalDateTime.now(); return localDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss:SSS")); } }
public class MyFilter implements Filter { @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { long start = System.currentTimeMillis(); HttpServletRequest request = (HttpServletRequest)servletRequest; filterChain.doFilter(servletRequest, servletResponse); long end = System.currentTimeMillis(); System.out.println(request.getRequestURI() + "執行時間:" + (end - start)); } }
自定義DateConfig,把前面這兩個加載進來:
@Configuration public class DateConfig { @Bean public DateUtil getDateUtil() { return new DateUtil(); } @Bean public FilterRegistrationBean registerFilter() { FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(); filterRegistrationBean.setFilter(new MyFilter()); filterRegistrationBean.addUrlPatterns("/*"); filterRegistrationBean.setName("costFilter"); filterRegistrationBean.setOrder(1); return filterRegistrationBean; } }
最后在spring.factories中寫入:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=util.spring.boot.autoconfigure.DateConfig
3.2.2 使用注解的方式
核心通過實現ImportSelector:
public class MyImport implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
return new String[]{DateConfig.class.getName()};
}
}
自定義注解:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Import(MyImport.class) public @interface EnableUtil { }
這個時候,可以不需要再spring.factories中配置,但是需要在啟動類上增加修飾注解:
@SpringBootApplication @EnableUtil public class Start { public static void main(String[] args) { SpringApplication.run(Start.class, args); } }
3.2.3 直接Import
@SpringBootApplication @Import(DateConfig.class) public class Start { public static void main(String[] args) { SpringApplication.run(Start.class, args); } }
對比上面三種方式,第一種最好,因為實現了零侵入,可插拔。
本文涉及的demo測試代碼可以參考gitee: https://gitee.com/leijisong/my-boot-starter
