springboot之自動裝配
前言
在最初接觸spring 的時候,還是使用xml進行裝配,我記得很清楚,當時分別配置了spring-dao.xml , spring-service.xml , spring-controller.xml。然後把所有需要用到的掃包,注入bean,以及配置,全都一股腦的塞進xml中,雖然出發點很好,不用在java程式碼中硬編碼了,但是xml的可讀性並不是很好,那陣子,真是痛苦的要命。
正文
後來逐漸的接觸到了spring boot,發現這個東西開發起來簡單的要命,幾乎自己不需要過多的考慮spring 與別的框架整合的問題,所有的一切spring boot已經幫我們做好了,只需要在application.properties 配置一下就完事。
但是spring boot 因為大部分的東西,已經不需要我們在著手了,所以對我們而言,“易學難精”。所以我決定好好把spring boot的原理搞清楚,今天就來說一說自動裝配。
這裡提前說一下,Spring boot的注入一共有四種方式:
1.通過 spring 模式註解 裝配
2.通過@Enable* 註解進行裝配
3.通過條件註解裝配
4.通過工廠類去載入
下面我會逐一說到以上這幾點。
通過 spring 模式註解 裝配
模式註解的方式,儘管我們剛開始接觸spring,spring mvc 框架,我們也應該會知道他們到底是怎麼使用的。
以@Component為首 派生的 @Controller,@Service, @Repository。分別對應了我們工程中的controller層,業務層,和資料持久層,這裡我就不做過多結束了,只要把註解放在類上就可以被自動的注入到spring 的context中。
通過@Enable* 註解進行裝配
很多人會問為什麼有了模式註解的裝備,還需要@Enable* 這個註解呢?
這個問題問的非常好,縱觀整個Web的發展,我們可以看出來,是一個化繁為簡的模組化趨勢。
所以@Enable* 也是具有同樣道理的,例如@EnableWebMvc就是一個web元件的集合。
@Enable*註解有很多種:
例如說 @EnableAutoConfiguration,@EnableWebMvc,@EnableCaching,@EnableAsync等。
@Enable* 的注入主要又可以分成兩種:
1.使用註解的方式注入
2.使用介面程式設計的方式注入
Tips:但是不管是哪一種方式都是通過 @Enable* 註解定義上的@Import註解引入的。
使用註解的實現(是在Serlet3.0出現的)就是說在@Enable*的@Import註解中,載入的那個類是被@Configuration注入的,例如@EnableWebMvc。
程式碼如下:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.springframework.web.servlet.config.annotation;
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.Import;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
//這就是上面說的哪個@Import註解,我們繼續檢視下DelegatingWebMvcConfiguration 這個類
@Import({DelegatingWebMvcConfiguration.class})
public @interface EnableWebMvc {
}
DelegatingWebMvcConfiguration 程式碼如下(只是摘取了部分,有所省略):
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.springframework.web.servlet.config.annotation;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.lang.Nullable;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.MessageCodesResolver;
import org.springframework.validation.Validator;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.HandlerExceptionResolver;
@Configuration
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
public DelegatingWebMvcConfiguration() {
}
@Autowired(
required = false
)
public void setConfigurers(List<WebMvcConfigurer> configurers) {
if (!CollectionUtils.isEmpty(configurers)) {
this.configurers.addWebMvcConfigurers(configurers);
}
}
protected void configurePathMatch(PathMatchConfigurer configurer) {
this.configurers.configurePathMatch(configurer);
}
protected void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
this.configurers.configureContentNegotiation(configurer);
}
//省略了之後的方法
}
在這裡可以很清楚的看到使用@Configuration(這個註解是替代原來在spring xml中注入而出現的註解)進行注入,雖然可以實現功能,但是每次只能注入指定的Bean。
使用程式設計的方式(是在Serlet3.1出現的)是在@Enable*的@Import註解中,載入的那個類,實現了ImportSelector介面,並且重寫 selectImports(AnnotationMetadata importingClassMetadata)方法,可以將需要注入的class名稱通過條件篩選後放在一個陣列中通過返回值進行注入,這是一個我們一直在用的註解,但是為什麼我們並不能在朝夕相處的spring boot 中找到相關的程式碼呢?因為Spring boot 對其進行了封裝,下面我們慢慢順藤摸瓜的去找一下。
新建立一個工程之後,映入眼簾的就是啟動類 *Application.java:
程式碼如下:
package com.harry.springtest;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringtestApplication {
public static void main(String[] args) {SpringApplication.run(SpringtestApplication.class, args);
}
}
在main方法中只有一個類的啟動方法,那麼我們就應該把關注點放在@SpringBootApplication上了
@SpringBootApplication 註解程式碼如下:
//
// 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.boot.SpringBootConfiguration;
import org.springframework.boot.context.TypeExcludeFilter;
import org.springframework.context.annotation.ComponentScan;
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 {};
}
在這裡我們可以看出來@SpringBootApplication是一個組合註解裡面運用到了@EnableAutoConfiguration,我們在繼續看一下@EnableAutoConfiguration:
package org.springframework.boot.autoconfigure;
//省略應該引入的包
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;
public AutoConfigurationImportSelector() {
}
}
//省略以下的所有方法。
DeferredImportSelector繼承了ImportSelector
spring 條件裝配
條件裝配一共有兩種方法,可以供我們使用:
1.使用@Profile註解
2.使用@Conditional註解
@Profile只是一個站在大局上的條件裝配,通過環境變數的某些值,或者是系統變數某些定值,我們可以使用不同的實現方法。這就類似於我們定義了一個介面,通過多型,定義了兩個實現類實現了相同的這個介面,但是我們可以通過環境變數去決定,到底是使用哪一個實現類去實現這個方法。
@Conditional註解,就更佳的靈活一些,類似於我們平常程式碼中的if else 通過判斷出來的boolean值,決定到底是不是應該注入這個bean。
@Conditional程式碼如下:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.springframework.context.annotation;
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;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
Class<? extends Condition>[] value();
}
我們可以看出來,需要往這個註解中加入一個值,這個值要實現Condition介面。
Condition介面的程式碼如下:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.springframework.context.annotation;
import org.springframework.core.type.AnnotatedTypeMetadata;
@FunctionalInterface
public interface Condition {
//關鍵之處
boolean matches(ConditionContext var1, AnnotatedTypeMetadata var2);
}
關鍵的地方,就在這裡了,我們在實現了這個介面的時候,得去重寫這個match方法,如果需要注入的地方,就應該返回true,如果是覺得不符合條件,不應該注入的就返回false。
spring boot 通過封裝 @Conditional 還有一些派生註解,如下:
@ConditionalOnBean(如果在當前context中存在這個bean,例項化bean)
@ConditionalOnClass(如果在classpath中有這個class,例項化 bean)
@ConditionalOnExpression(當表示式為true的時候,例項化一個bean)
@ConditionalOnMissingBean(如果在當前context中不存在這個bean,例項化一個bean)
@ConditionalOnMissingClass(如果在classpath中沒有這個class,例項化一個bean)
@ConditionalOnNotWebApplication(不是web應用,例項化bean)
spring 工廠載入機制
在auto-configuration這個jar包下,或者其他自動注入的jar下,我們都可以看到spring.factories這個檔案,例如說下圖:
正是通過這個檔案,我們把需要注入的類的全路徑配在這裡,但是問題來了,誰去讀這個檔案的呢?
在AutoConfigurationImportSelector.class中,我們可以看到以下方法:
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 這個方法:
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
繼續看loadSpringFactories 這個方法:
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
if (result != null) {
return result;
} else {
try {
//就在這個地方將META-INF/spring.factories讀入進來的。
Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
LinkedMultiValueMap result = new LinkedMultiValueMap();
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
Iterator var6 = properties.entrySet().iterator();
while(var6.hasNext()) {
Entry<?, ?> entry = (Entry)var6.next();
String factoryClassName = ((String)entry.getKey()).trim();
String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
int var10 = var9.length;
for(int var11 = 0; var11 < var10; ++var11) {
String factoryName = var9[var11];
result.add(factoryClassName, factoryName.trim());
}
}
}
cache.put(classLoader, result);
return result;
} catch (IOException var13) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
}
}
}
這下子終於真相大白了。
後記
通過這一次動手實踐,根據原始碼追根溯源,我才終於明白,年輕真的得多看看原始碼,因為原始碼能帶給我們的不光是在使用過程中減少bug的出現,以下解決bug的事件,更重要的是在設計上以及程式碼規範上的潛移默化,雖然說現在有一些還不是理解的很透徹,但是不積跬步,無以至千里。
在這裡也要感覺 慕課網上小馬哥的Spring Boot 2.0深度實踐之核心技術篇。
在收聽的過程中受益匪淺。
相關文章
- SpringBoot - 自動裝配Spring Boot
- SpringBoot | 2.1 SpringBoot自動裝配原理Spring Boot
- SpringBoot自動裝配原理分析Spring Boot
- SpringBoot系列--自動裝配原理Spring Boot
- SpringBoot自動裝配-原始碼分析Spring Boot原始碼
- SpringBoot自動裝配-自定義StartSpring Boot
- SpringBoot 自動裝配的原理分析Spring Boot
- SpringBoot系列--自動裝配原理2Spring Boot
- SpringBoot自動裝配原理解析Spring Boot
- springboot自動裝配(1)---@SpringBootApplication註解怎麼自動裝配各種元件Spring BootAPP元件
- SpringBoot自動裝配原理之Configuration以及@Bean註解的使用Spring BootBean
- Springboot學習日記(三)自動裝配Spring Boot
- SpringBoot是如何做到自動裝配的Spring Boot
- SpringBoot啟動程式碼和自動裝配原始碼分析Spring Boot原始碼
- SpringBoot自動裝配,比較全的吧,來看看吧~Spring Boot
- Spring自動裝配BeansSpringBean
- bean 的自動裝配Bean
- Spring Boot 自動裝配原理Spring Boot
- springboot 條件裝配Spring Boot
- 2、spring注入及自動裝配Spring
- springboot2.x基礎教程:自動裝配原理與條件註解Spring Boot
- Bean的自動裝配及作用域Bean
- Spring-04 Bean的自動裝配SpringBean
- 你來說一下springboot的啟動時的一個自動裝配過程吧Spring Boot
- Spring入門(二):自動化裝配beanSpringBean
- SpringBoot(14)—註解裝配BeanSpring BootBean
- 深入理解Spring框架的自動裝配原理Spring框架
- 常用的自動裝配註解@Autowired @RequiredArgsConstructor @AllArgsConstructorUIStruct
- Java開發學習(七)----DI依賴注入之自動裝配與集合注入Java依賴注入
- Spring入門(八):自動裝配的歧義性Spring
- 徹底搞明白Spring中的自動裝配和AutowiredSpring
- Spring框架使用@Autowired自動裝配引發的討論Spring框架
- 深度剖析Spring Boot自動裝配機制實現原理Spring Boot
- 玩轉SpringBoot之MyBatisplus自動化構建工具Spring BootMyBatis
- 聊聊springboot自動裝配出現的TypeNotPresentExceptionProxy異常排查Spring BootException
- SpringBoot(03)——自動配置Spring Boot
- SpringBoot | 自動配置原理Spring Boot
- SpringBoot(二)自動配置Spring Boot