一、前言
上一篇博客(細說Spring——IoC詳解(注解驅動開發之Bean的注入))中簡單的介紹了將組件注入容器的三種方法,這次我們就了解一下如何在包掃描時將不想要的組件排除,或者只添加特定的組件,然后我們學習一下FactoryBean的作用,不知道FactoryBean的可以參考一下:細說Spring——IoC詳解(FactoryBean、方法注入和方法替換)。
二、包掃描的過濾
使用@ComponentScan
指定要掃描的包,和使用xml
配置的包掃描大致類似使用excludeFilters
屬性添加排除的組件,使用includeFilters
屬性添加只要的組件,但是要使includeFilters
生效,必須先將useDefaultFilters
屬性設置為false
,和xml
配置類似,如果使用的是jdk8
以上的版本,可以定義多個@ComponentScan
,如果jdk8
以下的版本,可以使用@ComponentScans
,來裝多個@ComponentScan
達到相同的效果。
下面我們主要看一下怎么添加excludeFilters
和includeFilters
屬性,我們先看一下這兩個屬性的源碼是什么:
Filter[] includeFilters() default {};
/**
* Specifies which types are not eligible for component scanning.
* @see #resourcePattern
*/
Filter[] excludeFilters() default {};
我們可以看到這個個屬性的參數都是Filter
數組,這里的Filter
是在@ComponentScan
包里的一個內部注解,我們看一下源碼:
@Retention(RetentionPolicy.RUNTIME)
@Target({})
@interface Filter {
/**
* The type of filter to use.
* <p>Default is {@link FilterType#ANNOTATION}.
* @see #classes
* @see #pattern
*/
FilterType type() default FilterType.ANNOTATION;
/**
* Alias for {@link #classes}.
* @see #classes
*/
@AliasFor("classes")
Class<?>[] value() default {};
/**
* The class or classes to use as the filter.
* <p>The following table explains how the classes will be interpreted
* based on the configured value of the {@link #type} attribute.
* <table border="1">
* <tr><th>{@code FilterType}</th><th>Class Interpreted As</th></tr>
* <tr><td>{@link FilterType#ANNOTATION ANNOTATION}</td>
* <td>the annotation itself</td></tr>
* <tr><td>{@link FilterType#ASSIGNABLE_TYPE ASSIGNABLE_TYPE}</td>
* <td>the type that detected components should be assignable to</td></tr>
* <tr><td>{@link FilterType#CUSTOM CUSTOM}</td>
* <td>an implementation of {@link TypeFilter}</td></tr>
* </table>
* <p>When multiple classes are specified, <em>OR</em> logic is applied
* — for example, "include types annotated with {@code @Foo} OR {@code @Bar}".
* <p>Custom {@link TypeFilter TypeFilters} may optionally implement any of the
* following {@link org.springframework.beans.factory.Aware Aware} interfaces, and
* their respective methods will be called prior to {@link TypeFilter#match match}:
* <ul>
* <li>{@link org.springframework.context.EnvironmentAware EnvironmentAware}</li>
* <li>{@link org.springframework.beans.factory.BeanFactoryAware BeanFactoryAware}
* <li>{@link org.springframework.beans.factory.BeanClassLoaderAware BeanClassLoaderAware}
* <li>{@link org.springframework.context.ResourceLoaderAware ResourceLoaderAware}
* </ul>
* <p>Specifying zero classes is permitted but will have no effect on component
* scanning.
* @since 4.2
* @see #value
* @see #type
*/
@AliasFor("value")
Class<?>[] classes() default {};
/**
* The pattern (or patterns) to use for the filter, as an alternative
* to specifying a Class {@link #value}.
* <p>If {@link #type} is set to {@link FilterType#ASPECTJ ASPECTJ},
* this is an AspectJ type pattern expression. If {@link #type} is
* set to {@link FilterType#REGEX REGEX}, this is a regex pattern
* for the fully-qualified class names to match.
* @see #type
* @see #classes
*/
String[] pattern() default {};
}
我們可以看到內部使用FilterType
枚舉來表明了當前的Filter
是按照什么過濾的,這里常用的有三種:
-
FilterType.ANNOTATION
:按照注解來過濾bean -
FilterType.ASSIGNABLE_TYPE
:按照給定的類型 -
FilterType.CUSTOM
:按照自己給定的過濾器過濾
下面我們就挨個展示一下這三種的用法。首先是FilterType.ANNOTATION
,這個是按照注解來過濾,首先看一下在配置類:
@ComponentScan(value = "com.jiayifan.bean", excludeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {myAnno.class}),
//@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {BookService.class})
//@ComponentScan.Filter(type = FilterType.CUSTOM, classes = MyTypeFilter.class)
})
@Configuration//告訴spring這是一個配置類
public class MainConfig {
}
我們用包掃描往容器中注入組件,這里我就注入的兩個組件
Person
@Component
public class Person {}
Blue
@Component
@myAnno
public class Blue {
}
注意這里的Blue上面標有@myAnno
注解,而我們的配置類中使用excludeFilters = { @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {myAnno.class})}
把標有@myAnno
注解的組件排除了,現在我們看一下測試的結果:
@Test
public void importTest() {
printBeans();
}
可以看到果然沒有
blue
這個組件。
接下來我們看一下FilterType.ASSIGNABLE_TYPE
,這個是按照類型來過濾的,我們仍然使用上面的例子,只不過變化一下過濾的規則,只修改一下配置類:
@ComponentScan(value = "com.jiayifan.bean", excludeFilters = {
//@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {myAnno.class}),
@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {Person.class})
//@ComponentScan.Filter(type = FilterType.CUSTOM, classes = MyTypeFilter.class)
})
@Configuration//告訴spring這是一個配置類
public class MainConfig {
}
可以看到我把Pserosn
過濾了,這里看一下測試結果:
很明顯Person類已經被過濾了。
然后我們來學習一下FilterType.CUSTOM
,這個需要我們自己定義過濾的規則,我們需要自己實現一個過濾器,這個過濾器實現TypeFilter
接口,看一下我實現的一個過濾器:
/**
* Created by Yifan Jia on 2018/6/12.
* 自定的掃描規則
*/
public class MyTypeFilter implements TypeFilter{
/**
*
* @param metadataReader 讀取到的當前正在掃描的類的信息
* @param metadataReaderFactory 可以獲取到其他任何類信息
* @return
* @throws IOException
*/
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
//獲取當前類的直接信息
AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
//獲取當前正在掃描的類的信息
ClassMetadata classMetadata = metadataReader.getClassMetadata();
//獲取當前類的資源信息(類的路徑)
Resource resource = metadataReader.getResource();
//獲取當前類的全類名
String className = classMetadata.getClassName();
System.out.println("classMetadata: " + className);
if(className.contains("B")) {
return true;
}
return false;
}
}
上面的實現類中我的過濾邏輯就是過濾全類名中含有“B”的類,我們看一下配置類:
package com.jiayifan.config;
/**
* Created by Yifan Jia on 2018/6/12.
* 配置類代理xml配置文件
*/
@ComponentScan(value = "com.jiayifan.bean", excludeFilters = {
//@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {myAnno.class}),
// @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {Person.class})
@ComponentScan.Filter(type = FilterType.CUSTOM, classes = MyTypeFilter.class)
})
@Configuration//告訴spring這是一個配置類
public class MainConfig {
}
看一下測試結果:
我們在過濾器中還實現了將掃描到的類名打印出來的功能,可以看到我的com.jiayifan.bean
包中的類,然后掃描帶有@Component
的類加入到容器中,但是又將全類名中含有“B”字母的類排除,所以就只剩下person
組件了。
三、FactoryBean
還記得上一篇博客中最后說除了三種常用方法可以注入組件外,我們還有一種方法可以向容器中注入組件嗎,就是使用FactoryBean
。
首先我們需要先實現一個自己的FactoryBean
:
//創建一個Spring定義的工廠bean
public class ColorFactoryBean implements FactoryBean<Color> {
//返回一個color對象,這個對象會添加到容器中
//如果該bean是多實例的,就會在創建實例的時候調用getObjectType方法
public Color getObject() throws Exception {
System.out.println("ColorFactoryBean....getObject");
return new Color();
}
public Class<?> getObjectType() {
return Color.class;
}
//該bean是否是單實例的
public boolean isSingleton() {
return true;
}
}
然后我們將這個FactoryBean
注入容器:
@Configuration
public class MainConfig2 {
@Bean
public ColorFactoryBean colorFactoryBean() {
return new ColorFactoryBean();
}
}
然我我們看一下測試類:
@Test
public void importTest() {
printBeans();
}
private void printBeans() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
for(String name : beanDefinitionNames) {
System.out.println(name);
}
//工廠bean獲取的是調用getObject獲得的對象
Object colorFactoryBean = applicationContext.getBean("colorFactoryBean");
//Object colorFactoryBean2 = applicationContext.getBean("colorFactoryBean");
System.out.println("獲得是: " + colorFactoryBean.getClass());
}
測試結果:
我們可以看到我們在打印容器中有哪些組件時,容器中的是
colorFactoryBean
,可是在我們獲取到colorFactoryBean
這個組件時,發現獲取到的是color
,這個功能雖然我們并不常用,但是還是需要了解一下,我們如果就是想要獲得colorFactoryBean
,我們只需要:
Object colorFactoryBean = applicationContext.getBean("colorFactoryBean");
//前面加一個&
Object colorFactoryBean2 = applicationContext.getBean("&colorFactoryBean");
System.out.println("獲得是: " + colorFactoryBean.getClass());
System.out.println("獲得是: " + colorFactoryBean2.getClass());
然后獲取到的就是colorFactoryBean
: