靈魂拷問
- @ComponentScan註解是做什麼的?
- basePackages的方式和basePackageClasses的方式有什麼區別?你建議用哪個?為什麼?
- useDefaultFilters有什麼用?
- 常見的過濾器有哪些類型?說說你知道的幾個
- @ComponentScan是在哪個類中處理的?說一下大概的解析過程?
這些問題如果都ok,恭喜你,太優秀了,不知道沒關係,一起來看看。
背景介紹
到目前為止,介紹了2種註冊bean的方式:
- xml中bean元素的方式
- @Bean註解標註方法的方式
通常情況下,項目中大部分類都需要交給spring去管理,按照上面這2種方式,代碼量還是挺大的。
為了更方便bean的註冊,Spring提供了批量的方式註冊bean,方便大量bean批量註冊,spring中的@ComponentScan就是幹這個事情的。
@ComponentScan
@ComponentScan用於批量註冊bean。
這個註解會讓spring去掃描某些包及其子包中所有的類,然後將滿足一定條件的類作為bean註冊到spring容器容器中。
具體需要掃描哪些包?以及這些包中的類滿足什麼條件時被註冊到容器中,這些都可以通過這個註解中的參數動態配置。
先來看一下這個註解的定義:
<code>@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class) //@1
public @interface ComponentScan {
@AliasFor("basePackages")
String[] value() default {};
@AliasFor("value")
String[] basePackages() default {};
Class>[] basePackageClasses() default {};
Class extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
Class extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;
ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;
String resourcePattern() default "**/*.class";
boolean useDefaultFilters() default true;
Filter[] includeFilters() default {};
Filter[] excludeFilters() default {};
boolean lazyInit() default false;
}/<code>
定義上可以看出此註解可以用在任何類型上面,不過我們通常將其用在類上面。
常用參數:
value:指定需要掃描的包,如:com.javacode2018
basePackages:作用同value;value和basePackages不能同時存在設置,可二選一
basePackageClasses:指定一些類,spring容器會掃描這些類所在的包及其子包中的類
nameGenerator:自定義bean名稱生成器
resourcePattern:需要掃描包中的那些資源,默認是:**/*.class,即會掃描指定包中所有的class文件
useDefaultFilters:對掃描的類是否啟用默認過濾器,默認為true
includeFilters:過濾器:用來配置被掃描出來的那些類會被作為組件註冊到容器中
excludeFilters:過濾器,和includeFilters作用剛好相反,用來對掃描的類進行排除的,被排除的類不會被註冊到容器中
lazyInit:是否延遲初始化被註冊的bean
@1:@Repeatable(ComponentScans.class),這個註解可以同時使用多個。
@ComponentScan工作的過程:
- Spring會掃描指定的包,且會遞歸下面子包,得到一批類的數組
- 然後這些類會經過上面的各種過濾器,最後剩下的類會被註冊到容器中
所以玩這個註解,主要關注2個問題:
第一個:需要掃描哪些包?通過value、backPackages、basePackageClasses這3個參數來控制
第二:過濾器有哪些?通過useDefaultFilters、includeFilters、excludeFilters這3個參數來控制過濾器
這兩個問題搞清楚了,就可以確定哪些類會被註冊到容器中。
默認情況下,任何參數都不設置的情況下,此時,會將@ComponentScan修飾的類所在的包作為掃描包;默認情況下useDefaultFilters為true,這個為true的時候,spring容器內部會使用默認過濾器,規則是:凡是類上有@Repository、@Service、@Controller、@Component這幾個註解中的任何一個的,那麼這個類就會被作為bean註冊到spring容器中,所以默認情況下,只需在類上加上這幾個註解中的任何一個,這些類就會自動交給spring容器來管理了。
@Component、@Repository、@Service、@Controller
這幾個註解都是spring提供的。
先說一下@Component這個註解,看一下其定義:
<code>@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface Component {
String value() default "";
}/<code>
從定義中可以看出,這個註解可以用在任何類型上面。
通常情況下將這個註解用在類上面,標註這個類為一個組件,默認情況下,被掃描的時候會被作為bean註冊到容器中。
value參數:被註冊為bean的時候,用來指定bean的名稱,如果不指定,默認為類名首字母小寫。如:類UserService對應的beanname為userService
再來看看@Repository源碼如下:
<code>@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Repository {
@AliasFor(annotation = Component.class)
String value() default "";
}/<code>
Repository上面有@Component註解。
value參數上面有@AliasFor(annotation = Component.class),設置value參數的時候,也相當於給@Component註解中的value設置值。
其他兩個註解@Service、@Controller源碼和@Repository源碼類似。
這4個註解本質上是沒有任何差別,都可以用在類上面,表示這個類被spring容器掃描的時候,可以作為一個bean組件註冊到spring容器中。
spring容器中對這4個註解的解析並沒有進行區分,統一採用@Component註解的方式進行解析,所以這幾個註解之間可以相互替換。
spring提供這4個註解,是為了讓系統更清晰,通常情況下,系統是分層結構的,多數系統一般分為controller層、service層、dao層。
@controller通常用來標註controller層組件,@service註解標註service層的組件,@Repository標註dao層的組件,這樣可以讓整個系統的結構更清晰,當看到這些註解的時候,會和清晰的知道屬於哪個層,對於spring來說,將這3個註解替換成@Component註解,對系統沒有任何影響,產生的效果是一樣的。
下面通過案例來感受@ComponentScan各種用法。
案例1:任何參數未設置
UserController
<code>package com.javacode2018.lesson001.demo22.test1.controller;
import org.springframework.stereotype.Controller;
@Controller
public class UserController {
}/<code>
UserService
<code>package com.javacode2018.lesson001.demo22.test1.service;
import org.springframework.stereotype.Service;
@Service
public class UserService {
}/<code>
UserDao
<code>package com.javacode2018.lesson001.demo22.test1.dao;
import org.springframework.stereotype.Repository;
@Repository
public class UserDao {
}/<code>
UserModel
<code>package com.javacode2018.lesson001.demo22.test1;
import org.springframework.stereotype.Component;
@Component
public class UserModel {
}/<code>
上面幾個類中,分別使用了4種註解。
@CompontentScan修飾的類
<code>package com.javacode2018.lesson001.demo22.test1;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan
public class ScanBean1 {
}/<code>
上面幾個類的結構圖
fsdfds
測試用例
<code>package com.javacode2018.lesson001.demo22;
import com.javacode2018.lesson001.demo22.test1.ScanBean1;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class ComponentScanTest {
@Test
public void test1() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ScanBean1.class);
for (String beanName : context.getBeanDefinitionNames()) {
System.out.println(beanName + "->" + context.getBean(beanName));
}
}
}/<code>
@1:使用AnnotationConfigApplicationContext作為ioc容器,將ScanBean作為參數傳入。
默認會掃描ScanBean類所在的包中的所有類,類上有@Component、@Repository、@Service、@Controller任何一個註解的都會被註冊到容器中
運行輸出
部分輸出如下:
<code>userModel->com.javacode2018.lesson001.demo22.test1.UserModel@595b007d
userController->com.javacode2018.lesson001.demo22.test1.controller.UserController@72d1ad2e
userDao->com.javacode2018.lesson001.demo22.test1.dao.UserDao@2d7275fc
userService->com.javacode2018.lesson001.demo22.test1.service.UserService@399f45b1/<code>
注意最後4行這幾個bean,都被註冊成功了。
案例2:指定需要掃描的包
指定需要掃毛哪些包,可以通過value或者basePackage來配置,二者選其一,都配置運行會報錯,下面我們通過value來配置。
ScanBean2
<code>package com.javacode2018.lesson001.demo22.test2;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan({
"com.javacode2018.lesson001.demo22.test1.controller",
"com.javacode2018.lesson001.demo22.test1.service"
})
public class ScanBean2 {
}/<code>
上面指定了2需要掃描的包,這兩個包中有2個類。
測試用例
ComponentScanTest中新增個方法
<code>@Test
public void test2() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ScanBean2.class);
for (String beanName : context.getBeanDefinitionNames()) {
System.out.println(beanName + "->" + context.getBean(beanName));
}
}/<code>
運行輸出
截取了關鍵幾行如下:
<code>userController->com.javacode2018.lesson001.demo22.test1.controller.UserController@dd8ba08
userService->com.javacode2018.lesson001.demo22.test1.service.UserService@245b4bdc/<code>
可以看出只有controller包和service包中的2個類被註冊為bean了。
注意
指定包名的方式掃描存在的一個隱患,若包被重名了,會導致掃描會失效,一般情況下面我們使用basePackageClasses的方式來指定需要掃描的包,這個參數可以指定一些類型,默認會掃描這些類所在的包及其子包中所有的類,這種方式可以有效避免這種問題。
下面來看一下basePackageClasses的方式。
案例:basePackageClasses指定掃描範圍
我們可以在需要掃描的包中定義一個標記的接口或者類,他們的唯一的作用是作為basePackageClasses的值,其他沒有任何用途。
下面我們定義這樣一個接口
<code>package com.javacode2018.lesson001.demo22.test6.beans;
public interface ScanClass {
}/<code>
再來定義2個類,用@Component註解標記
<code>package com.javacode2018.lesson001.demo22.test6.beans;
import org.springframework.stereotype.Component;
@Component
public class Service1 {
}/<code>
<code>package com.javacode2018.lesson001.demo22.test6.beans;
import org.springframework.stereotype.Component;
@Component
public class Service2 {
}/<code>
來一個@CompontentScan標記的類
<code>package com.javacode2018.lesson001.demo22.test6;
import com.javacode2018.lesson001.demo22.test6.beans.ScanClass;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan(basePackageClasses = ScanClass.class)
public class ScanBean6 {
}/<code>
測試用例
ComponentScanTest中新增個方法
<code>@Test
public void test6() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ScanBean6.class);
for (String beanName : context.getBeanDefinitionNames()) {
System.out.println(beanName + "->" + context.getBean(beanName));
}
}/<code>
運行輸出
<code>service1->com.javacode2018.lesson001.demo22.test6.beans.Service1@79924b
service2->com.javacode2018.lesson001.demo22.test6.beans.Service2@7b9a4292/<code>
includeFilters的使用
用法
再來看一下includeFilters這個參數的定義:
<code>Filter[] includeFilters() default {};/<code>
是一個Filter類型的數組,多個Filter之間為或者關係,即滿足任意一個就可以了,看一下Filter的代碼:
<code>@Retention(RetentionPolicy.RUNTIME)
@Target({})
@interface Filter {
FilterType type() default FilterType.ANNOTATION;
@AliasFor("classes")
Class>[] value() default {};
@AliasFor("value")
Class>[] classes() default {};
String[] pattern() default {};
}/<code>
可以看出Filter也是一個註解,參數:
type:過濾器的類型,是個枚舉類型,5種類型
ANNOTATION:通過註解的方式來篩選候選者,即判斷候選者是否有指定的註解
ASSIGNABLE_TYPE:通過指定的類型來篩選候選者,即判斷候選者是否是指定的類型
ASPECTJ:ASPECTJ表達式方式,即判斷候選者是否匹配ASPECTJ表達式
REGEX:正則表達式方式,即判斷候選者的完整名稱是否和正則表達式匹配
CUSTOM:用戶自定義過濾器來篩選候選者,對候選者的篩選交給用戶自己來判斷
value:和參數classes效果一樣,二選一
classes:3種情況如下
當type=FilterType.ANNOTATION時,通過classes參數可以指定一些註解,用來判斷被掃描的類上是否有classes參數指定的註解
當type=FilterType.ASSIGNABLE_TYPE時,通過classes參數可以指定一些類型,用來判斷被掃描的類是否是classes參數指定的類型
當type=FilterType.CUSTOM時,表示這個過濾器是用戶自定義的,classes參數就是用來指定用戶自定義的過濾器,自定義的過濾器需要實現org.springframework.core.type.filter.TypeFilter接口
pattern:2種情況如下
當type=FilterType.ASPECTJ時,通過pattern來指定需要匹配的ASPECTJ表達式的值
當type=FilterType.REGEX時,通過pattern來自正則表達式的值
案例:掃描包含註解的類
需求
我們自定義一個註解,讓標註有這些註解的類自動註冊到容器中
代碼實現
下面的代碼都在com.javacode2018.lesson001.demo22.test3包中。
定義一個註解
<code>package com.javacode2018.lesson001.demo22.test3;
import java.lang.annotation.*;
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyBean {
}/<code>
創建一個類,使用這個註解標註
<code>package com.javacode2018.lesson001.demo22.test3;
@MyBean
public class Service1 {
}/<code>
再來一個類,使用spring中的`@Compontent`標註
<code>package com.javacode2018.lesson001.demo22.test3;
import org.springframework.stereotype.Component;
@Component
public class Service2 {
}/<code>
再來一個類,使用@CompontentScan標註
<code>package com.javacode2018.lesson001.demo22.test3;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
@ComponentScan(includeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = MyBean.class)
})
public class ScanBean3 {
}/<code>
上面指定了Filter的type為註解的類型,只要類上面有@MyBean註解的,都會被作為bean註冊到容器中。
測試用例
ComponentScanTest中新增個測試用例
<code>@Test
public void test3() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ScanBean3.class);
for (String beanName : context.getBeanDefinitionNames()) {
System.out.println(beanName + "->" + context.getBean(beanName));
}
}/<code>
運行輸出,截取了主要的幾行
<code>service1->com.javacode2018.lesson001.demo22.test3.Service1@6b81ce95
service2->com.javacode2018.lesson001.demo22.test3.Service2@2a798d51/<code>
Service1上標註了@MyBean註解,被註冊到容器了,但是Service2上沒有標註@MyBean啊,怎麼也被註冊到容器了?
原因:Service2上標註了@Compontent註解,而@CompontentScan註解中的useDefaultFilters默認是true,表示也會啟用默認的過濾器,而默認的過濾器會將標註有@Component、@Repository、@Service、@Controller這幾個註解的類也註冊到容器中
如果我們只想將標註有@MyBean註解的bean註冊到容器,需要將默認過濾器關閉,即:useDefaultFilters=false,我們修改一下ScanBean3的代碼如下:
<code>@ComponentScan(
useDefaultFilters = false, //不啟用默認過濾器
includeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = MyBean.class)
})
public class ScanBean3 {
}/<code>
再次運行test3輸出:
<code>service1->com.javacode2018.lesson001.demo22.test3.Service1@294425a7/<code>
擴展:自定義註解支持定義bean名稱
上面的自定義的@MyBean註解,是無法指定bean的名稱的,可以對這個註解做一下改造,加個value參數來指定bean的名稱,如下:
<code>@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Component //@1
public @interface MyBean {
@AliasFor(annotation = Component.class) //@2
String value() default ""; //@3
}/<code>
重點在於@1和@2這2個地方的代碼,通過上面的參數可以間接給@Component註解中的value設置值。
這塊用到了@AliasFor註解,對這塊不瞭解的,可以去看一下:java註解詳解及spring對註解的增強
修改一下Service1的代碼:
<code>@MyBean("service1Bean")
public class Service1 {
}/<code>
運行test3用例輸出:
<code>service1Bean->com.javacode2018.lesson001.demo22.test3.Service1@222545dc/<code>
此時bean名稱就變成了service1Bean。
案例:包含指定類型的類
下面的代碼都位於com.javacode2018.lesson001.demo22.test4包中。
來個接口
<code>package com.javacode2018.lesson001.demo22.test4;
public interface IService {
}/<code>
讓spring來進行掃描,類型滿足IService的都將其註冊到容器中。
來2個實現類
<code>package com.javacode2018.lesson001.demo22.test4;
public class Service1 implements IService {
}/<code>
<code>package com.javacode2018.lesson001.demo22.test4;
public class Service2 implements IService {
}/<code>
來一個@CompontentScan標註的類
<code>package com.javacode2018.lesson001.demo22.test4;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
@ComponentScan(
useDefaultFilters = false, //不啟用默認過濾器
includeFilters = {
@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = IService.class) //@1
})
public class ScanBean4 {
}/<code>
@1:被掃描的類滿足IService.class.isAssignableFrom(被掃描的類)條件的都會被註冊到spring容器中
來個測試用例
ComponentScanTest中新增個測試用例
<code>@Test
public void test4() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ScanBean4.class);
for (String beanName : context.getBeanDefinitionNames()) {
System.out.println(beanName + "->" + context.getBean(beanName));
}
}/<code>
運行輸出
<code>service1->com.javacode2018.lesson001.demo22.test4.Service1@6379eb
service2->com.javacode2018.lesson001.demo22.test4.Service2@294425a7/<code>
自定義Filter
用法
有時候我們需要用到自定義的過濾器,使用自定義過濾器的步驟:
<code>1.設置@Filter中type的類型為:FilterType.CUSTOM
2.自定義過濾器類,需要實現接口:org.springframework.core.type.filter.TypeFilter
3.設置@Filter中的classses為自定義的過濾器類型/<code>
來看一下TypeFilter這個接口的定義:
<code>@FunctionalInterface
public interface TypeFilter {
boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
throws IOException;
}/<code>
是一個函數式接口,包含一個match方法,方法返回boolean類型,有2個參數,都是接口類型的,下面介紹一下這2個接口。
MetadataReader接口
類元數據讀取器,可以讀取一個類上的任意信息,如類上面的註解信息、類的磁盤路徑信息、類的class對象的各種信息,spring進行了封裝,提供了各種方便使用的方法。
看一下這個接口的定義:
<code>public interface MetadataReader {
/**
* 返回類文件的資源引用
*/
Resource getResource();
/**
* 返回一個ClassMetadata對象,可以通過這個讀想獲取類的一些元數據信息,如類的class對象、是否是接口、是否有註解、是否是抽象類、父類名稱、接口名稱、內部包含的之類列表等等,可以去看一下源碼
*/
ClassMetadata getClassMetadata();
/**
* 獲取類上所有的註解信息
*/
AnnotationMetadata getAnnotationMetadata();
}/<code>
MetadataReaderFactory接口
類元數據讀取器工廠,可以通過這個類獲取任意一個類的MetadataReader對象。
源碼:
<code>public interface MetadataReaderFactory {
/**
* 返回給定類名的MetadataReader對象
*/
MetadataReader getMetadataReader(String className) throws IOException;
/**
* 返回指定資源的MetadataReader對象
*/
MetadataReader getMetadataReader(Resource resource) throws IOException;
}/<code>
自定義Filter案例
需求
我們來個自定義的Filter,判斷被掃描的類如果是IService接口類型的,就讓其註冊到容器中。
代碼實現
來個自定義的TypeFilter類:
<code>package com.javacode2018.lesson001.demo22.test5;
import com.javacode2018.lesson001.demo22.test4.IService;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.TypeFilter;
import java.io.IOException;
public class MyFilter implements TypeFilter {
/**
* @param metadataReader
* @param metadataReaderFactory
* @return
* @throws IOException
*/
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
Class curClass = null;
try {
//當前被掃描的類
curClass = Class.forName(metadataReader.getClassMetadata().getClassName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//判斷curClass是否是IService類型
boolean result = IService.class.isAssignableFrom(curClass);
return result;
}
}/<code>
來一個@CompontentScan標註的類
<code>package com.javacode2018.lesson001.demo22.test5;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
@ComponentScan(
basePackages = {"com.javacode2018.lesson001.demo22.test4"},
useDefaultFilters = false, //不啟用默認過濾器
includeFilters = {
@ComponentScan.Filter(type = FilterType.CUSTOM, classes = MyFilter.class) //@1
})
public class ScanBean5 {
}/<code>
@1:type為FilterType.CUSTOM,表示Filter是用戶自定義的,classes為自定義的過濾器
再來個測試用例
ComponentScanTest中新增個測試用例
<code>@Test
public void test5() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ScanBean5.class);
for (String beanName : context.getBeanDefinitionNames()) {
System.out.println(beanName + "->" + context.getBean(beanName));
}
}/<code>
運行輸出
<code>service1->com.javacode2018.lesson001.demo22.test4.Service1@4cc451f2
service2->com.javacode2018.lesson001.demo22.test4.Service2@6379eb/<code>
excludeFilters
配置排除的過濾器,滿足這些過濾器的類不會被註冊到容器中,用法上面和includeFilters用一樣,這個我就不演示了,可以自己玩玩
@ComponentScan重複使用
從這個註解的定義上可以看出這個註解可以同時使用多個,如:
<code>@ComponentScan(basePackageClasses = ScanClass.class)
@ComponentScan(
useDefaultFilters = false, //不啟用默認過濾器
includeFilters = {
@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = IService.class)
})
public class ScanBean7 {
}/<code>
還有一種寫法,使用@ComponentScans的方式:
<code>@ComponentScans({
@ComponentScan(basePackageClasses = ScanClass.class),
@ComponentScan(
useDefaultFilters = false, //不啟用默認過濾器
includeFilters = {
@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = IService.class)
})})
public class ScanBean7 {
}/<code>
Spring中這塊的源碼
@CompontentScan註解是被下面這個類處理的
<code>org.springframework.context.annotation.ConfigurationClassPostProcessor/<code>
這個類非常非常關鍵,主要用戶bean的註冊,前面我們介紹的@Configuration,@Bean註解也是被這個類處理的。
還有下面這些註解:
<code>@PropertySource
@Import
@ImportResource
@Compontent/<code>
以上這些註解都是被ConfigurationClassPostProcessor這個類處理的,內部會遞歸處理這些註解,完成bean的註冊。
以@CompontentScan來說一下過程,第一次掃描之後會得到一批需要註冊的類,然後會對這些需要註冊的類進行遍歷,判斷是否有上面任意一個註解,如果有,會將這個類交給ConfigurationClassPostProcessor繼續處理,直到遞歸完成所有bean的註冊。
想成為高手,這個類是必看的。
總結
- @ComponentScan用於批量註冊bean,spring會按照這個註解的配置,遞歸掃描指定包中的所有類,將滿足條件的類批量註冊到spring容器中
- 可以通過value、basePackages、basePackageClasses 這幾個參數來配置包的掃描範圍
- 可以通過useDefaultFilters、includeFilters、excludeFilters這幾個參數來配置類的過濾器,被過濾器處理之後剩下的類會被註冊到容器中
- 指定包名的方式配置掃描範圍存在隱患,包名被重命名之後,會導致掃描實現,所以一般我們在需要掃描的包中可以創建一個標記的接口或者類,作為basePackageClasses的值,通過這個來控制包的掃描範圍
- @CompontScan註解會被ConfigurationClassPostProcessor類遞歸處理,最終得到所有需要註冊的類。
會不定期更新幹貨 喜歡的可以轉發加關注哦
閱讀更多 JAVA互聯搬磚工人 的文章