Spring註解開發01--------元件註冊
注入元件的幾種方式
在Spring中,有如下四種方式像容器中注入元件:
- 包掃描 + 元件標註註解(@Controller,@Service, @Repository, @Component)
- @Bean註解實現
- @Import註解實現
- 使用Spring提供的FactoryBean
下面我們來詳細聊聊以上四種方式。
前期準備
1.引入Spring相關依賴
<dependencies> <!-- Spring5.0之後,Spring相關依賴都包含在spring-webmv這個以來之中--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.5</version> </dependency> <!-- junit測試 --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.2</version> <scope>test</scope> </dependency> </dependencies>
- 我們使用的Spring版本為5.3.5,Spring5.0之後,Spring的一些核心依賴都包含在Spring-mvc當中,所以在匯入依賴的時候,我們只需要匯入spring-webmvc這一個即可!
- junit我們用來作為單元測試
檢視專案依賴,發現我們需要的jar包已經全部匯入!
2.專案結構設計
3.編寫Spring配置類,並使用@Configuration將配置檔案注入到ioc容器中
package com.xdw.config; import org.springframework.context.annotation.Configuration; @Configuration public class MainConfigOfComponent { }
4.編寫測試類,檢視配置檔案是否註冊成功
import com.xdw.config.MainConfigOfComponent; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class TestBeanComponent { @Test public void test01() { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfComponent.class); printAllBeanNames(applicationContext); } public void printAllBeanNames(ApplicationContext applicationContext) { String[] names = applicationContext.getBeanDefinitionNames(); for (String name : names) { System.out.println(name); } } }
- 這裡因為我們使用的是配置類的方法,所以獲取ioc容器使用的是
AnnotationConfigApplicationContext
,而不是之前xml檔案配置時使用的ClassPathXmlApplicationContext
applicationContext.getBeanDefinitionNames();
方法用來獲取所有在容器中的bean的id@Configuration
本質也是一個@Component註解,生成的bean的id預設時類名(首字母小寫)
測試,發現我們編寫的配置類id已經成功註冊到ioc容器中
方式一: 包掃描 + 元件標註註解
測試
1.新建測試類(Controller, Service, Dao),分別使用註解@Controller,@Service, @Repository修飾
BookController:
package com.xdw.controller;
import org.springframework.stereotype.Controller;
@Controller
public class BookController {
}
BookService:
package com.xdw.service;
import org.springframework.stereotype.Service;
@Service
public class BookService {
}
BookDao:
package com.xdw.dao;
import org.springframework.stereotype.Repository;
@Repository
public class BookDao {
}
2.在配置類上使用註解 @ComponentScan,指定需要自動掃描的包
@ComponentScan(value={"com.xdw"})
@Configuration
public class MainConfigOfComponent {
}
3.編寫測試方法,執行,檢視結果!
public class TestBeanComponent {
@Test
public void test01() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfComponent.class);
printAllBeanNames(applicationContext);
}
public void printAllBeanNames(ApplicationContext applicationContext) {
String[] names = applicationContext.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}
}
}
執行測試方法,結果如下:
可以發現,我們剛剛新建的幾個類已經成功注入到ioc容器中了!
@ComponentScan 註解屬性說明
在上面的測試中,我們通過使用@ComponentScan自動包掃描註解 + 元件標註註解(@Controller,@Service, @Repository, @Component)完成了我們的元件註冊!
@ComponentScan 註解提供了很多屬性,供我們靈活的註冊元件至容器中。
-
value屬性: 用來指定要掃描的包。上面的例子中我們設定為"com.xdw",表示掃描com.xdw包下的所有類及子包下的所有類
-
excludeFilters屬性:掃描的時候按照指定規則排除某些元件
-
includeFilters = Filter[]; 指定掃描的時候只需要包含哪些元件,使用的時候一定要useDefaultFilters設為false
-
我們也可以使用@ComponentScans註解來配置多個@ComponentScan
-
excludeFilters與includeFilters屬性都需要配合@Filter註解使用,@Filter為我們提供了以下幾種方式進行元件的過濾或者選擇:
FilterType.ANNOTATION: 按照註解(常用)
FilterType.ASSIGNABLE_TYPE:表示按照指定型別(也比較常用)
FilterType.ASPECTJ: 使用ASPECTJ表示式,不常用
FilterType.REGEX: 使用正則表示式
FilterType.CUSTOM: 自定義規則
幾種過率選擇方式例項
因為excludeFilters與includeFilters屬性使用起來基本一樣,我們這裡就是用excludeFilters屬性進行測試。
我們選擇比較常用的以下三種進行測試:
1.FilterType.ANNOTATION: 按照註解
在配置類MainConfigOfComponent
新增如下註解:
@ComponentScan(value={"com.xdw"},
includeFilters = {@Filter(type= FilterType.ANNOTATION, value= Controller.class)},
useDefaultFilters = false)
@Configuration
public class MainConfigOfComponent {
}
注意:使用includeFilters 屬性時,一定要設定useDefaultFilters = false,否則includeFilters 不會生效!
測試執行:
發現只有bookController注入容器成功!
2.FilterType.ASSIGNABLE_TYPE 按照指定型別
修改配置類如下:
@ComponentScan(value={"com.xdw"},
includeFilters = {@Filter(type= FilterType.ASSIGNABLE_TYPE, value= BookService.class)},
useDefaultFilters = false)
@Configuration
public class MainConfigOfComponent {
}
測試執行:
發現當前只有bookService成功註冊到容器中
3.FilterType.CUSTOM自定義型別
點開FilterType列舉類,我們發現
/** Filter candidates using a given custom
* {@link org.springframework.core.type.filter.TypeFilter} implementation.
*/
CUSTOM
要使用自定義型別,我們需要編寫一個類來實現TypeFilter介面。
編寫MyTypeFilter類,實現TypeFilter介面:
package com.xdw.filter;
import org.springframework.core.io.Resource;
import org.springframework.core.type.AnnotationMetadata;
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 MyTypeFilter implements TypeFilter {
/**
*
* @param metadataReader 當前掃描到的類資訊
* @param metadataReaderFactory 可以讀取到其他任何類的資訊
*
*/
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
// 獲取當前類的所有註解資訊
AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
// 獲取當前掃描類的資訊
ClassMetadata classMetadata = metadataReader.getClassMetadata();
// 獲取當前類路徑資訊
Resource resource = metadataReader.getResource();
// 這裡我們使用名稱,如果包含Dao就注入到ioc容器中
if(classMetadata.getClassName().contains("Dao")) {
return true;
}
return false;
}
}
修改配置類,設定includeFilters,Filter的type設定為CUSTOM,value設定為我們剛剛編寫的MyTypeFilter.class
@ComponentScan(value={"com.xdw"},
includeFilters = {@Filter(type= FilterType.CUSTOM, value= MyTypeFilter.class)},
useDefaultFilters = false)
@Configuration
public class MainConfigOfComponent {
}
測試:
我們發現只有bookDao成功註冊到容器中!
總結
1. @ComponentScan
要與 @Controller
,@Service
, @Repository
, @Component
等註解配合使用
2. @ComponentScan
的includeFilters與excludeFilters屬性使用時,需要結合Filter[]使用
3. 使用@ComponentScan
的includeFilters屬性時,一定要同時設定useDefaultFilters = false否則不會生效
方式二:@Bean注入元件
測試
1.建立Person類
package com.xdw.pojo;
public class Person {
private Integer age;
private String name;
public Person() {
}
public Person(Integer age, String name) {
this.age = age;
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}
2.編寫測試類
package com.xdw.config;
import com.xdw.pojo.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@ComponentScan(value={"com.xdw"})
@Configuration
public class MainConfigOfComponent {
@Bean
public Person person() {
return new Person(10, "法外狂徒:張三");
}
}
3.編寫測試類,執行測試
import com.xdw.config.MainConfigOfComponent;
import com.xdw.pojo.Person;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class TestBeanComponent {
@Test
public void test01() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfComponent.class);
printAllBeanNames(applicationContext);
Person person = (Person) applicationContext.getBean("person");
System.out.println(person);
}
public void printAllBeanNames(ApplicationContext applicationContext) {
String[] names = applicationContext.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}
}
}
測試,結果如下:
發現我們剛建立的類已經成功注入到ioc容器中了。
@Bean註解說明
1.型別就是返回值,id預設就是方法名
2.修改id有兩種方式: a.修改方法名 b.為@Bean
設定name或者value屬性
@Bean(value="person01")
public Person person() {
return new Person(10, "法外狂徒:張三");
}
Bean的作用域@Scope
bean例項有如下四種作用域:
- singleton: 單例項的,ioc啟動時就會建立物件放入容器中,以後每次獲取就直接從容器中獲取(map.get()),常用
- prototype: 多例項的,ioc容器啟動時不會建立物件注入ioc容器中,以後每次獲取都會呼叫方法建立
- request: 同一次請求建立一個例項
- session: 同一個Session建立一個例項
在不指定作用域的情況下,預設式單例模式。
單例模式測試
配置類方法:
// 不指定作用域預設是單例模式
@Bean
public Person person() {
System.out.println("person物件開始建立");
return new Person(10, "法外狂徒:張三");
}
編寫測試方法:
@Test
public void test02() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfComponent.class);
System.out.println("容器初始化完畢");
}
執行測試,結果如下:
我們發現,單例模式下,預設容器初始化的時候,物件就已經被載入進ioc容器中了。
修改測試方法如下:
@Test
public void test02() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfComponent.class);
System.out.println("容器初始化完畢");
Person person01 = (Person)applicationContext.getBean("person");
Person person02 = (Person) applicationContext.getBean("person");
System.out.println(person01 == person02);
}
測試,結果如下:
我們發現我們從容器中取了兩次person取出的兩個物件為同一個!
我們在建立bean的方法上新增@Lazy
註解,程式碼如下:
@Lazy // 表示我們在第一次獲取bean時,容器才會注入bean至容器中
@Bean
public Person person() {
System.out.println("person物件開始建立");
return new Person(10, "法外狂徒:張三");
}
執行,測試結果如下:
容器啟動的時候,沒有建立物件放入容器中,在我們第一次呼叫時,物件才被建立並放入容器中。
總結:
1. @Bean注入到容器中的bean,不指定作用域,預設是單例模式
2. 單例模式預設是容器啟動時就建立物件放入容器中,我們可以使用@Lazy
懶載入註解,讓我們在第一次使用時,才建立物件並初始化。
3. 單例模式下,容器中只存在一個bean,無論取出多少次,取出的物件都為同一個。
多例項模式測試
為建立bean的方法新增如下註解:
@Scope("prototype")
@Bean
public Person person() {
System.out.println("person物件開始建立");
return new Person(10, "法外狂徒:張三");
}
測試代如下:
@Test
public void test02() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfComponent.class);
System.out.println("容器初始化完畢");
Person person01 = (Person)applicationContext.getBean("person");
Person person02 = (Person) applicationContext.getBean("person");
System.out.println(person01 == person02);
}
執行, 測試結果如下:
總結:
1. 多例項模式,容器在初始化的時候,不會建立例項
2. 每次去取bean的時候,容器都會新建一個bean
request與session兩種模式我們基本用不上,在這裡不做過多的說明。
@Conditional註解
我們可以使用 @Conditional註解,按照一定條件進行判斷,滿足條件給容器注入bean。
我們檢視@Conditional
原始碼
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
/**
* All {@link Condition} classes that must {@linkplain Condition#matches match}
* in order for the component to be registered.
*/
Class<? extends Condition>[] value();
}
發現該註解存在value屬性,該屬性值必須必須繼承Condition介面。
測試
要求:我們新建一類SystemData,在配置類中編寫兩個建立bean的方法,id分別為windows,linux;根據系統型別,如果時windows系統,就建立id分別為windows的例項,如果是linux,就建立id為linux的例項。
1.編寫兩個自定義的condition類,實現condition介面:
package com.xdw.condition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;
public class WindowsCondition implements Condition {
/**
*
*
* @param context 判斷條件能使用的上下文(環境)
* @param metadata 註釋
*
*/
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 1.獲取ioc使用的beanFactory
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
// 2. 獲取類載入器
ClassLoader classLoader = context.getClassLoader();
// 3.獲取當前環境資訊
Environment environment = context.getEnvironment();
// 4.獲取到bean定義的註冊類,可以判斷容器中bean的註冊情況,也可以給容器中註冊bean
BeanDefinitionRegistry registry = context.getRegistry();
// BeanDefinition definition = registry.getBeanDefinition("person");
String property = environment.getProperty("os.name");
if(property.contains("Windows")) {
return true;
}
return false;
}
}
package com.xdw.condition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;
public class LinuxCondition implements Condition {
/**
*
*
* @param context 判斷條件上下文(環境)
* @param metadata 元資料
* @return {@code true} if the condition matches and the component can be registered,
* or {@code false} to veto the annotated component's registration
*/
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 1.獲取ioc使用的beanFactory
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
// 2. 獲取類載入器
ClassLoader classLoader = context.getClassLoader();
// 3.獲取當前環境資訊
Environment environment = context.getEnvironment();
// 4.獲取到bean定義的註冊類,可以判斷容器中bean的註冊情況,也可以給容器中註冊bean
BeanDefinitionRegistry registry = context.getRegistry();
String property = environment.getProperty("os.name");
if(property.contains("Linux")) {
return true;
}
return false;
}
}
2.在配置類中新增兩個註冊bean的方法
@Conditional(value={WindowsCondition.class})
@Bean("windows")
public SystemData systemData01() {
System.out.println("windows已經建立");
return new SystemData("windows系統!");
}
@Conditional(value={LinuxCondition.class})
@Bean("linux")
public SystemData systemData02() {
System.out.println("linux已經建立");
return new SystemData("linux系統!");
}
執行測試, 結果如下:
我們看出因為我們當前系統是windows,所以id為windows的bean被建立並注入到容器中了!
總結
1. @Bean註解,預設物件名稱是方法名,物件型別為方法的返回型別;物件名修改可以通過@Bean註解的name屬性或者直接修改方法名實現。
2. @Bean註解預設的作用域是單例模式,可以通過@Scope
註解來修改
3. 單例模式預設是容器啟動時建立並初始化,我們可以使用@Lazy
註解實現懶載入,第一次使用時才建立並初始化bean。
4. 我們可以使用@Conditional註解,來實現滿足一定條件才載入並初始化bean。這個註解既可以放在方法上,也可以放在類上.
方式三:使用@Import註解
我們可以使用@Import快速給容器中匯入一個元件。
直接使用@Import
新建一個Color類:
package com.xdw.pojo;
public class Color {
}
在配置類上新增如下註解:
@Import(value={Color.class})
@Configuration
public class MainConfigOfComponent {}
編寫測試方法,打印出所有註冊到容器中的bean,執行測試結果如下:
我們發現剛剛建立的類已經成功注入到容器中。
總結: @Import(要匯入到容器中的元件),容器中會自動註冊這些元件,id預設是全類名
ImportSlector
這種方法需要實現ImportSlector
介面,介面路中需要重寫selectImports方法,該方法會返回一個需要例項化的陣列。
新增兩個類:
package com.xdw.pojo;
public class Red {
}
package com.xdw.pojo;
public class Blue {
}
編寫MyImportSelector
類,實現ImportSlector
介面如下:
package com.xdw.selector;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"com.xdw.pojo.Red", "com.xdw.pojo.Blue"};
}
}
這個類重寫了selectImports方法,將我們剛剛新建的類的全類名返回。
修改配置類上的@Import註解,將我們建立MyImportSelector
新增進@Import註解中:
@Import(value={Color.class, MyImportSelector.class})
@Configuration
public class MainConfigOfComponent {
測試,執行結果如下:
我們剛剛新建的類已經成功注入到容器中。
總結:該種方式建立的物件名預設也是全類名。
ImportBeanDefinitionRegistrar
我們還可以通過ImportBeanDefinitionRegistrar,手動註冊bean至容器中。
新建一個類RainBow
:
package com.xdw.pojo;
public class RainBow {
}
編寫類MyImportBeanDefinitionRegistrar
實現ImportBeanDefinitionRegistrar
介面:
package com.xdw.selector;
import com.xdw.pojo.RainBow;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
/**
*
* AnnotationMetadata: 當前類的註解資訊
* BeanDefinitionRegistry:BeanDefinition註冊類
* 把所有需要新增進容器中得bean: 呼叫
* BeanDefinitionRegistry.registerBeanDefinition()方法手動註冊進來
* @param registry current bean definition registry
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
if(registry.containsBeanDefinition("com.xdw.pojo.Red") && registry.containsBeanDefinition("com.xdw.pojo.Blue")) {
RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(RainBow.class);
registry.registerBeanDefinition("rainBow", rootBeanDefinition);
}
}
}
這裡我們加了一個簡單的判斷,如果ioc容器中存在名稱為"com.xdw.pojo.Red"與"com.xdw.pojo.Blue"的物件時,就載入rainBow物件。
將我們的編寫的MyImportBeanDefinitionRegistrar
類新增進@Import註解中:
@Import(value={Color.class, MyImportSelector.class, MyImportBeanDefinitionRegistrar.class})
@Configuration
public class MainConfigOfComponent {}
測試:
我們新建的rainBow成功載入至ioc容器中。
總結
@Import有如下三種方式到bean至容器中:
1. @Import(要匯入到容器中的元件),容器中會自動註冊這些元件,id預設是全類名
2. ImportSlector:返回需要匯入的元件的全類名陣列(這種方式在Springboot中用得比較多)
3. ImportBeanDefinitionRegistrar:手動註冊bean到容器中
方式四: 使用Spring提供的FactoryBean
測試
我們還使用之前的Color類,註釋掉之前的@Import。
編寫ColorFactoryBean類,實現FactoryBean介面:
package com.xdw.factory;
import com.xdw.pojo.Color;
import org.springframework.beans.factory.FactoryBean;
public class ColorFactoryBean implements FactoryBean {
/**
* 是否是單例模式: true 單例模式 false多例項模式
*
* @return
*/
@Override
public boolean isSingleton() {
return true;
}
/**
* 相當於class
* @return
* @throws Exception
*/
@Override
public Object getObject() throws Exception {
return new Color();
}
@Override
public Class<?> getObjectType() {
return Color.class;
}
}
編寫配置類,使用@Bean註解註冊我們剛剛建立的ColorFactoryBean
@Configuration
public class MainConfigOfComponent {
@Bean
public ColorFactoryBean colorFactoryBean() {
return new ColorFactoryBean();
}
}
編寫測試類:
@Test
public void test04() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfComponent.class);
// 實際上獲取到的是工廠bean呼叫getObject建立的物件
Object factoryBean = applicationContext.getBean("colorFactoryBean");
System.out.println(factoryBean.getClass());
// 獲取工廠類本身
Object bean = applicationContext.getBean("&colorFactoryBean");
System.out.println(bean.getClass());
}
執行結果:
總結
使用Spring提供的FactoryBean
1. 預設獲取到的是工廠bean呼叫getObject建立的物件(使用名稱獲取)
2. 要獲取工廠bean本身,我們需要給前面加一個&