前言
SpringBoot中提供了很多Enable開頭的注解,這些注解都是用于動态啟用某些功能的,而其底層原理是使用@Import注解導入一些配置類,比如實作Bean的動态加載。這句話聽起來稀裡糊塗,那麼我們來思考一個問題:SpringBoot工程是否可以直接擷取jar包中定義的Bean?帶着問題我們來一起刨析下SpringBoot自動配置之@Enable*注解的源碼。
驗證
1、首先簡單看一下啟動注解@SpringBootApplication
點進去源碼如下:
@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 {
...
}
解釋:
@Target({ElementType.TYPE}) ----------聲明注解作用範圍是類;
@Retention(RetentionPolicy.RUNTIME)—聲明注解作用時機是運作時;
@Documented---------------------------聲明生成javaDoc文檔;
@SpringBootConfiguration--------------點進去,源碼如下,說明@SpringBootConfiguration是一個組合注解,本質其實就是一個@Configuration,也就是一個配置類的标記,這也是為什麼SpringBoot主配置類中可以建立Bean的根本原因:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration //這個注解的核心
@Indexed
public @interface SpringBootConfiguration {
@AliasFor(
annotation = Configuration.class
)
boolean proxyBeanMethods() default true;
}
@EnableAutoConfiguration--------------點進去看源碼,發現它也是一個組合注解,有一個@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 {};
}
問題 SpringBoot工程是否可以直接擷取jar包中定義的Bean?
1、建立兩個子產品示範
本文分别建立springboot-enable、springboot-embedded兩個子產品,前者代表我們的springBoot項目,後者模拟三方jar包
編寫springboot-embedded
1、定義Bean類User.java
public class User {
}
2、定義配置類UserConfig.java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class UserConfig {
@Bean
public User user(){
return new User();
}
}
3、編寫springboot-enable
1、pom.xml中引入springboot-embedded的坐标依賴
<dependency>
<groupId>com.test</groupId>
<artifactId>springboot-embedded</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
2、啟動類
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
public class SpringbootEnableApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);
User user = (User)context.getBean("user");
System.out.println(user);
}
}
啟動後,報錯
nosuch bean
說明,SpringBoot工程是不可以直接擷取jar包中定義的Bean,那麼問題又來了,為什麼我們在pom.xml中引入Redis的坐标以後,卻可以直接使用名為redisTemplate這樣一個對象呢?根本原因就是@Import這個注解
SpringBoot實作使用第三方jar包中定義的Bean
上面已經驗證SpringBoot工程是不可以直接擷取jar包中定義的Bean,原因就是啟動類上的@SpringBootApplication注解内部組合之一的@ComponentScan是個掃包範圍注解,預設掃包範圍是掃描被@SpringBootApplication注解的啟動類的同級包及其子包,我們發現啟動類的包為com.itlean,而UserConfig.java的包是com.embedded.config,并沒有同級或者包含關系,想要啟動時候能掃描到UserConfig.java,有3中解決方式,具體如下:
1,第一種方式(掃包範圍)
修改啟動類,添加掃包範圍注解,将UserConfig.java所在包進行手動掃描:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
@ComponentScan("com.test.config") //UserConfig.java手動掃描加入IOC容器
public class SpringbootEnableApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);
User user = (User)context.getBean("user");
System.out.println(user);
}
}
但是這種方式是不是太粗放了,我用你一個對象,完了我還得知道這個對象所在的包,那我要用到Redis的redisTemplate對象,我還得一頓找這玩意在哪個包,然後在啟動類的@ComponentScan注解中添加掃包,肯定是不現實的
2、第二種方式@Import注解
先看一下源碼,發現入參是Class<?>[] value();也就是一堆類的數組,多一嘴哈,在SpringBoot中使用@Import注解引入的這些類都會被加載到IOC容器中。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
Class<?>[] value();
}
既然如上所說,那我們把啟動類注解再一次修改如下:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;
@SpringBootApplication
@Import(UserConfig.class)//直接将User的配置類引入
public class SpringbootEnableApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);
User user = (User)context.getBean("user");
System.out.println(user);
}
}
這種方式要比方式一間接很多了,不需要知道被使用Bean的包路徑,但是還是要記住配置類的名字,我用一個對象,還需要知道這個對象對應的配置類叫什麼,顯然還是不夠友好,接下來方式三就是@Import用法的終極優化。
3、第三種方式@Import注解封裝
在第三方jar中建立一個注解類,對@Import注解進行封裝:
import org.springframework.context.annotation.Import;
import java.lang.annotation.*;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(UserConfig.class)// 在第三方中直接将UserConfig.class引入,調用方就不需要知道包路徑和配置類名稱了
public @interface EnableUser {
}
調用方啟動類修改如下:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
@EnableUser //這種使用起來已經非常簡潔了
public class SpringbootEnableApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);
User user = (User)context.getBean("user");
System.out.println(user);
}
}
實際開發中,都是使用最後這種方式。
SpringBoot中@Enable*注解
以上第三種方式就是SpringBoot底層自動裝配的原理,是以說@Enable*這類注解是開啟某些功能的注解,底層是使用@Import方式來實作Bean的動态加載,不使用@EnableUser這個注解,我就不能使用User這個對象。再回過頭來看啟動類的注解@SpringBootAppliction注解,内部注解組合中有一個@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 {
...
}
@EnableAutoConfiguration注解内部呢又使用@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 {};
}