傳統方式的SSM框架由於須要配置大量文件而被開發人員詬病重複性工做,因此SpringBoot的出如今減小開發人員作大量重複性配置的工做,使得開發人員可以快速的開始項目開發、更加專一於業務代碼的編寫。但SpringBoot跟SSM有什麼框架不一樣呢?爲何SpringBoot能夠自動裝配呢?SpringBoot自動裝配是如何實現的呢?html
寫過SpringBoot應用的開發者都知道,SpringBoot應用的啓動類是被@SpringBootApplication註解的類,以下我寫的一個demo.後續的自動裝配源碼也是從這個demo入口進入的。java
package com.chandler.auto_configure.demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
1.主入口只有一個main方法,SpringApplication.run()方法。
2.@SpringBootApplication.web
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.boot.SpringBootConfiguration; import org.springframework.boot.context.TypeExcludeFilter; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.FilterType; import org.springframework.context.annotation.ComponentScan.Filter; import org.springframework.core.annotation.AliasFor; @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 { @AliasFor( annotation = EnableAutoConfiguration.class ) Class<?>[] exclude() default {}; @AliasFor( annotation = EnableAutoConfiguration.class ) String[] excludeName() default {}; @AliasFor( annotation = ComponentScan.class, attribute = "basePackages" ) String[] scanBasePackages() default {}; @AliasFor( annotation = ComponentScan.class, attribute = "basePackageClasses" ) Class<?>[] scanBasePackageClasses() default {}; @AliasFor( annotation = Configuration.class ) boolean proxyBeanMethods() default true; }
SpringBootApplication是一個組合註解,@SpringBootConfiguration、@EnableAutoConfiguratio、@ComponentScan都在包含咋該註解中。spring
package org.springframework.boot; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.AliasFor; @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Configuration public @interface SpringBootConfiguration { @AliasFor( annotation = Configuration.class ) boolean proxyBeanMethods() default true; } @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface Configuration { @AliasFor( annotation = Component.class ) String value() default ""; boolean proxyBeanMethods() default true; }
該註解開啓了自動裝配功能,咱們進入看看具體是如何實現的。mvc
@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 {}; } /** * Indicates that the package containing the annotated class should be registered with * {@link AutoConfigurationPackages}. * * @author Phillip Webb * @since 1.3.0 * @see AutoConfigurationPackages */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Import(AutoConfigurationPackages.Registrar.class) public @interface AutoConfigurationPackage { }
EnableAutoConfigureation註解包含了@AutoConfigurationPackage,@AutoConfigurationPackage中在容器中@Import了一個AutoConfigurationPackages.Registrar.class,咱們繼續看看這個Registrar.class。app
/** * {@link ImportBeanDefinitionRegistrar} to store the base package from the importing * configuration. */ 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)); } }
Registrar.獲取掃描的包路徑。獲取的就是被@SpringBootApplication註解類所在包以及子包。因此包名必定要注意,在主配置類(@SpringBootApplication註解的類)。如今以及知道從哪裏去加載了,但還有一個問題就是加載誰??EnableAutoConfigureation註解中還@Import了一個AutoConfigurationImportSelector.class。
框架
有了這些自動裝配的功能,就省去了配置的重複性工做,但這些配置又是從哪裏讀取得呢?默認狀況下都會有一套默認配置。這些配置是Spring-boot-autoconfiguration中META-INF下的spring.factories.
ide
/** * {@link EnableAutoConfiguration Auto-configuration} for {@link EnableWebMvc Web MVC}. * * @author Phillip Webb * @author Dave Syer * @author Andy Wilkinson * @author Sébastien Deleuze * @author Eddú Meléndez * @author Stephane Nicoll * @author Kristine Jetzke * @author Bruce Brouwer * @author Artsiom Yudovin * @since 2.0.0 */ @Configuration(proxyBeanMethods = false) @ConditionalOnWebApplication(type = Type.SERVLET) @ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class }) @ConditionalOnMissingBean(WebMvcConfigurationSupport.class) @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10) @AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class }) public class WebMvcAutoConfiguration { public static final String DEFAULT_PREFIX = ""; public static final String DEFAULT_SUFFIX = ""; private static final String[] SERVLET_LOCATIONS = { "/" }; @Bean @ConditionalOnMissingBean(HiddenHttpMethodFilter.class) @ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false) public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() { return new OrderedHiddenHttpMethodFilter(); } @Bean @ConditionalOnMissingBean(FormContentFilter.class) @ConditionalOnProperty(prefix = "spring.mvc.formcontent.filter", name = "enabled", matchIfMissing = true) public OrderedFormContentFilter formContentFilter() { return new OrderedFormContentFilter(); } static String[] getResourceLocations(String[] staticLocations) { String[] locations = new String[staticLocations.length + SERVLET_LOCATIONS.length]; System.arraycopy(staticLocations, 0, locations, 0, staticLocations.length); System.arraycopy(SERVLET_LOCATIONS, 0, locations, staticLocations.length, SERVLET_LOCATIONS.length); return locations; } // Defined as a nested config to ensure WebMvcConfigurer is not read when not // on the classpath @Configuration(proxyBeanMethods = false) @Import(EnableWebMvcConfiguration.class) @EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class }) @Order(0) public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer { private static final Log logger = LogFactory.getLog(WebMvcConfigurer.class); private final ResourceProperties resourceProperties; private final WebMvcProperties mvcProperties; private final ListableBeanFactory beanFactory; private final ObjectProvider<HttpMessageConverters> messageConvertersProvider; final ResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer; public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties, WebMvcProperties mvcProperties, ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider, ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider) { this.resourceProperties = resourceProperties; this.mvcProperties = mvcProperties; this.beanFactory = beanFactory; this.messageConvertersProvider = messageConvertersProvider; this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider.getIfAvailable(); } @Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { this.messageConvertersProvider .ifAvailable((customConverters) -> converters.addAll(customConverters.getConverters())); } @Override public void configureAsyncSupport(AsyncSupportConfigurer configurer) { if (this.beanFactory.containsBean(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME)) { Object taskExecutor = this.beanFactory .getBean(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME); if (taskExecutor instanceof AsyncTaskExecutor) { configurer.setTaskExecutor(((AsyncTaskExecutor) taskExecutor)); } } Duration timeout = this.mvcProperties.getAsync().getRequestTimeout(); if (timeout != null) { configurer.setDefaultTimeout(timeout.toMillis()); } } @Override public void configurePathMatch(PathMatchConfigurer configurer) { configurer.setUseSuffixPatternMatch(this.mvcProperties.getPathmatch().isUseSuffixPattern()); configurer.setUseRegisteredSuffixPatternMatch( this.mvcProperties.getPathmatch().isUseRegisteredSuffixPattern()); } @Override public void configureContentNegotiation(ContentNegotiationConfigurer configurer) { WebMvcProperties.Contentnegotiation contentnegotiation = this.mvcProperties.getContentnegotiation(); configurer.favorPathExtension(contentnegotiation.isFavorPathExtension()); configurer.favorParameter(contentnegotiation.isFavorParameter()); if (contentnegotiation.getParameterName() != null) { configurer.parameterName(contentnegotiation.getParameterName()); } Map<String, MediaType> mediaTypes = this.mvcProperties.getContentnegotiation().getMediaTypes(); mediaTypes.forEach(configurer::mediaType); } @Bean @ConditionalOnMissingBean public InternalResourceViewResolver defaultViewResolver() { InternalResourceViewResolver resolver = new InternalResourceViewResolver(); resolver.setPrefix(this.mvcProperties.getView().getPrefix()); resolver.setSuffix(this.mvcProperties.getView().getSuffix()); return resolver; } @Bean @ConditionalOnBean(View.class) @ConditionalOnMissingBean public BeanNameViewResolver beanNameViewResolver() { BeanNameViewResolver resolver = new BeanNameViewResolver(); resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 10); return resolver; } @Bean @ConditionalOnBean(ViewResolver.class) @ConditionalOnMissingBean(name = "viewResolver", value = ContentNegotiatingViewResolver.class) public ContentNegotiatingViewResolver viewResolver(BeanFactory beanFactory) { ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver(); resolver.setContentNegotiationManager(beanFactory.getBean(ContentNegotiationManager.class)); // ContentNegotiatingViewResolver uses all the other view resolvers to locate // a view so it should have a high precedence resolver.setOrder(Ordered.HIGHEST_PRECEDENCE); return resolver; } @Bean @ConditionalOnMissingBean @ConditionalOnProperty(prefix = "spring.mvc", name = "locale") public LocaleResolver localeResolver() { if (this.mvcProperties.getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) { return new FixedLocaleResolver(this.mvcProperties.getLocale()); } AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver(); localeResolver.setDefaultLocale(this.mvcProperties.getLocale()); return localeResolver; } @Override public MessageCodesResolver getMessageCodesResolver() { if (this.mvcProperties.getMessageCodesResolverFormat() != null) { DefaultMessageCodesResolver resolver = new DefaultMessageCodesResolver(); resolver.setMessageCodeFormatter(this.mvcProperties.getMessageCodesResolverFormat()); return resolver; } return null; } @Override public void addFormatters(FormatterRegistry registry) { ApplicationConversionService.addBeans(registry, this.beanFactory); } @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { if (!this.resourceProperties.isAddMappings()) { logger.debug("Default resource handling disabled"); return; } Duration cachePeriod = this.resourceProperties.getCache().getPeriod(); CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl(); if (!registry.hasMappingForPattern("/webjars/**")) { customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**") .addResourceLocations("classpath:/META-INF/resources/webjars/") .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl)); } String staticPathPattern = this.mvcProperties.getStaticPathPattern(); if (!registry.hasMappingForPattern(staticPathPattern)) { customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern) .addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations())) .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl)); } } private Integer getSeconds(Duration cachePeriod) { return (cachePeriod != null) ? (int) cachePeriod.getSeconds() : null; } private void customizeResourceHandlerRegistration(ResourceHandlerRegistration registration) { if (this.resourceHandlerRegistrationCustomizer != null) { this.resourceHandlerRegistrationCustomizer.customize(registration); } } @Bean @ConditionalOnMissingBean({ RequestContextListener.class, RequestContextFilter.class }) @ConditionalOnMissingFilterBean(RequestContextFilter.class) public static RequestContextFilter requestContextFilter() { return new OrderedRequestContextFilter(); } } /** * Configuration equivalent to {@code @EnableWebMvc}. */ @Configuration(proxyBeanMethods = false) public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware { private final ResourceProperties resourceProperties; private final WebMvcProperties mvcProperties; private final ListableBeanFactory beanFactory; private final WebMvcRegistrations mvcRegistrations; private ResourceLoader resourceLoader; public EnableWebMvcConfiguration(ResourceProperties resourceProperties, ObjectProvider<WebMvcProperties> mvcPropertiesProvider, ObjectProvider<WebMvcRegistrations> mvcRegistrationsProvider, ListableBeanFactory beanFactory) { this.resourceProperties = resourceProperties; this.mvcProperties = mvcPropertiesProvider.getIfAvailable(); this.mvcRegistrations = mvcRegistrationsProvider.getIfUnique(); this.beanFactory = beanFactory; } @Bean @Override public RequestMappingHandlerAdapter requestMappingHandlerAdapter( @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager, @Qualifier("mvcConversionService") FormattingConversionService conversionService, @Qualifier("mvcValidator") Validator validator) { RequestMappingHandlerAdapter adapter = super.requestMappingHandlerAdapter(contentNegotiationManager, conversionService, validator); adapter.setIgnoreDefaultModelOnRedirect( this.mvcProperties == null || this.mvcProperties.isIgnoreDefaultModelOnRedirect()); return adapter; } @Override protected RequestMappingHandlerAdapter createRequestMappingHandlerAdapter() { if (this.mvcRegistrations != null && this.mvcRegistrations.getRequestMappingHandlerAdapter() != null) { return this.mvcRegistrations.getRequestMappingHandlerAdapter(); } return super.createRequestMappingHandlerAdapter(); } @Bean @Primary @Override public RequestMappingHandlerMapping requestMappingHandlerMapping( @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager, @Qualifier("mvcConversionService") FormattingConversionService conversionService, @Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) { // Must be @Primary for MvcUriComponentsBuilder to work return super.requestMappingHandlerMapping(contentNegotiationManager, conversionService, resourceUrlProvider); } @Bean public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext, FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) { WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping( new TemplateAvailabilityProviders(applicationContext), applicationContext, getWelcomePage(), this.mvcProperties.getStaticPathPattern()); welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider)); return welcomePageHandlerMapping; } private Optional<Resource> getWelcomePage() { String[] locations = getResourceLocations(this.resourceProperties.getStaticLocations()); return Arrays.stream(locations).map(this::getIndexHtml).filter(this::isReadable).findFirst(); } private Resource getIndexHtml(String location) { return this.resourceLoader.getResource(location + "index.html"); } private boolean isReadable(Resource resource) { try { return resource.exists() && (resource.getURL() != null); } catch (Exception ex) { return false; } } @Bean @Override public FormattingConversionService mvcConversionService() { WebConversionService conversionService = new WebConversionService(this.mvcProperties.getDateFormat()); addFormatters(conversionService); return conversionService; } @Bean @Override public Validator mvcValidator() { if (!ClassUtils.isPresent("javax.validation.Validator", getClass().getClassLoader())) { return super.mvcValidator(); } return ValidatorAdapter.get(getApplicationContext(), getValidator()); } @Override protected RequestMappingHandlerMapping createRequestMappingHandlerMapping() { if (this.mvcRegistrations != null && this.mvcRegistrations.getRequestMappingHandlerMapping() != null) { return this.mvcRegistrations.getRequestMappingHandlerMapping(); } return super.createRequestMappingHandlerMapping(); } @Override protected ConfigurableWebBindingInitializer getConfigurableWebBindingInitializer( FormattingConversionService mvcConversionService, Validator mvcValidator) { try { return this.beanFactory.getBean(ConfigurableWebBindingInitializer.class); } catch (NoSuchBeanDefinitionException ex) { return super.getConfigurableWebBindingInitializer(mvcConversionService, mvcValidator); } } @Override protected ExceptionHandlerExceptionResolver createExceptionHandlerExceptionResolver() { if (this.mvcRegistrations != null && this.mvcRegistrations.getExceptionHandlerExceptionResolver() != null) { return this.mvcRegistrations.getExceptionHandlerExceptionResolver(); } return super.createExceptionHandlerExceptionResolver(); } @Override protected void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) { super.extendHandlerExceptionResolvers(exceptionResolvers); if (this.mvcProperties.isLogResolvedException()) { for (HandlerExceptionResolver resolver : exceptionResolvers) { if (resolver instanceof AbstractHandlerExceptionResolver) { ((AbstractHandlerExceptionResolver) resolver).setWarnLogCategory(resolver.getClass().getName()); } } } } @Bean @Override public ContentNegotiationManager mvcContentNegotiationManager() { ContentNegotiationManager manager = super.mvcContentNegotiationManager(); List<ContentNegotiationStrategy> strategies = manager.getStrategies(); ListIterator<ContentNegotiationStrategy> iterator = strategies.listIterator(); while (iterator.hasNext()) { ContentNegotiationStrategy strategy = iterator.next(); if (strategy instanceof PathExtensionContentNegotiationStrategy) { iterator.set(new OptionalPathExtensionContentNegotiationStrategy(strategy)); } } return manager; } @Override public void setResourceLoader(ResourceLoader resourceLoader) { this.resourceLoader = resourceLoader; } } @Configuration(proxyBeanMethods = false) @ConditionalOnEnabledResourceChain static class ResourceChainCustomizerConfiguration { @Bean ResourceChainResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer() { return new ResourceChainResourceHandlerRegistrationCustomizer(); } } interface ResourceHandlerRegistrationCustomizer { void customize(ResourceHandlerRegistration registration); } static class ResourceChainResourceHandlerRegistrationCustomizer implements ResourceHandlerRegistrationCustomizer { @Autowired private ResourceProperties resourceProperties = new ResourceProperties(); @Override public void customize(ResourceHandlerRegistration registration) { ResourceProperties.Chain properties = this.resourceProperties.getChain(); configureResourceChain(properties, registration.resourceChain(properties.isCache())); } private void configureResourceChain(ResourceProperties.Chain properties, ResourceChainRegistration chain) { Strategy strategy = properties.getStrategy(); if (properties.isCompressed()) { chain.addResolver(new EncodedResourceResolver()); } if (strategy.getFixed().isEnabled() || strategy.getContent().isEnabled()) { chain.addResolver(getVersionResourceResolver(strategy)); } if (properties.isHtmlApplicationCache()) { chain.addTransformer(new AppCacheManifestTransformer()); } } private ResourceResolver getVersionResourceResolver(ResourceProperties.Strategy properties) { VersionResourceResolver resolver = new VersionResourceResolver(); if (properties.getFixed().isEnabled()) { String version = properties.getFixed().getVersion(); String[] paths = properties.getFixed().getPaths(); resolver.addFixedVersionStrategy(version, paths); } if (properties.getContent().isEnabled()) { String[] paths = properties.getContent().getPaths(); resolver.addContentVersionStrategy(paths); } return resolver; } } /** * Decorator to make {@link PathExtensionContentNegotiationStrategy} optional * depending on a request attribute. */ static class OptionalPathExtensionContentNegotiationStrategy implements ContentNegotiationStrategy { private static final String SKIP_ATTRIBUTE = PathExtensionContentNegotiationStrategy.class.getName() + ".SKIP"; private final ContentNegotiationStrategy delegate; OptionalPathExtensionContentNegotiationStrategy(ContentNegotiationStrategy delegate) { this.delegate = delegate; } @Override public List<MediaType> resolveMediaTypes(NativeWebRequest webRequest) throws HttpMediaTypeNotAcceptableException { Object skip = webRequest.getAttribute(SKIP_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST); if (skip != null && Boolean.parseBoolean(skip.toString())) { return MEDIA_TYPE_ALL_LIST; } return this.delegate.resolveMediaTypes(webRequest); } } }
這樣是否是就不少配置都不須要咱們自行配置了,SPringboot已經給咱們配置好了。源碼分析