一篇文章,讓你告別xml(Spring 註解開發)


在學習spring中的註解開發之前,必須瞭解java中的反射和註解:

1. 組件添加相關注解

當一個項目很大的時候,xml 文件就會有很多,配置起來就稍微有點麻煩,所以,可以使用註解來代替在xml的相關配置。

1.1 @Configuration

用在類上,指明這個類是配置類,相當於是一個只有名稱空間的xml文件,有了配置類,就可以不用寫xml文件了。

1.2 @Bean

用在方法上,給IOC容器中註冊一個bean,id 默認爲方法名,也可以使用value屬性指定,類型是方法的返回值類型。相當於在xml文件中寫<bean id=" " class=" "/></bean>。如果方法中有參數,參數默認從容器中獲取。
`

@//表示這個類是配置類,代替xml文件
@Configuration
public class MainConfig {
    @Bean //在IOC容器中註冊bean,類型是返回值類型,id默認是方法名
    public Person person(){
        return new Person("張三",20);
    }
}

1.3 @ComponentScan

用在配置類上,開啓包掃描,開啓之後,類只要標註了@Controller、@Service、@repository、@Componmente
這四個註解中的任何一個,都會在容器中註冊,相當於在xml文件中使用<context:componment-scan value=" ">

  • 常用屬性:
  • value:指定要掃描的包
  • excludeFilters :指定掃描的時候按照什麼規則排除哪些組件,這個屬性的類型是一個Filter註解類型的數組,Filter註解中,有type(按照什麼類型進行排除)、classes(排除type類型的類)等屬性
@ComponentScan(value="ewen",
       excludeFilters = {
       排除@Repository註解
       @Filter(type=FilterType.ANNOTATION,classes={Repository.class})
})
  • includeFilters:指定掃描的時候只包含那些類型的組件,使用這個屬性的時候,需要讓useDefaultFilters=false(@CoponentScan的屬性),來禁用默認的過濾規則,默認使用的是不掃描哪些些組件
@ComponentScan(value="ewen",
includeFilters ={
        //只要@Service註解
        @Filter(type=FilterType.ANNOTATION, classes={Service.class, Repository.class})
},  useDefaultFilters = false)

@Filter這個註解中,type的類型是FilterType枚舉類,枚舉類中有以下幾個值

  • FilterType.ANNOTATION:按照註解(常用),classes是註解.class
  • FilterType.ASSIGNABLE_TYPE:按照給定的類型(常用),classes是類名.class
  • FilterType.ASPECTJ:使用ASPECTJ表達式
  • FilterType.REGEX:使用正則指定
  • FilterType.CUSTOM:使用自定義規則(自定義類,實現FilterType接口),classes爲自定義類.class

自定義過濾規則,就是寫一個類,實現FilterType接口,實現match方法(過濾規則),當掃描一個類時,會執行match方法,如果返回true,表示,符合規則,將這個類在IOC容器中註冊,返回false,不在容器中註冊。

@ComponentScan(value="ewen",
includeFilters = {
              @Filter(type=FilterType.CUSTOM, classes={MyTypeFilter.class})
      } , useDefaultFilters = false
)

自定義規則(只要含有 “er” 的類)

public class MyTypeFilter implements TypeFilter {
    /**
     * @param metadataReader:讀取到的當前正在掃描的類信息
     * @param metadataReaderFactory:可以獲取到其他類的任何信息
     * @return 當進行包掃描時,使用自定義過濾規則,掃描到的類來與這個方法中定義的規則匹配
     *          如果返回true,代表符合規則,將這個類注入到容器中,返回false,代表不符合規則
     * @throws IOException
     */
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
        //獲得當前正在掃描類的註解信息
        AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
        //獲取當前正在掃描的類信息
        ClassMetadata classMetadata = metadataReader.getClassMetadata();
        //獲取當前正在掃描類的路徑
        Resource resource = metadataReader.getResource();
        String name = classMetadata.getClassName();
        if(name.contains("er")){
            return true;
        }
        return false;
    }
}

1.4 @Scope

指定bean的作用範圍,有如下四個屬性:

  • singleton:單實例的,默認值,會在IOC容器啓動的時候,創建對象(執行@Bean下的方法),並且,只創建一次,每一次拿到的是同一個對象
  • prototype:多實例的,從容器中獲取對象的時候創建,每次獲取,都會執行@Bean下的方法,得到不同的對象
  • request:同一次請求創建一個實例(很少用)
  • session:同一個session創建一個實例(很少用)

1.5 @Lazy

懶加載,容器啓動時,不創建對象,獲取時纔會創建,只有當是bean是單例的(singleton)時纔有用

1.6 @Conditional

用在方法上,按照一定條件進行判斷,滿足條件給容器註冊bean,注意需要和@Bean一起使用。也可以把這個註解放在類上,類中的bean滿足條件纔可以在容器中註冊
需要定義一個條件類,實現Condition接口,實現match方法,如果這個方法返回true,則符合條件,註冊bean

	//如果是Windows操作系統,給容器中註冊bill
    @Conditional(WindowsCondition.class)
    @Bean
    public Person person01(){
        return new Person("bill", 60);
    }
	

自定義條件類

//判斷當前操作系統是不是Windows
public class WindowsCondition implements Condition {
    /**
     *
     * @param conditionContext:判斷條件能使用的上下文環境
     * @param annotatedTypeMetadata:註釋信息
     * @return 如果返回true,代表符合條件,返回false,表示不符合條件
     */
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        /*通過conditionContext可以獲得很多有用的信息, 可以使用這些信息,來設置很多條件
        //1.獲取IOC使用的工廠BeanFactory
        ConfigurableListableBeanFactory beanFactory = conditionContext.getBeanFactory();
        //2.獲取類加載器
        ClassLoader classLoader = conditionContext.getClassLoader();
          //4.獲取bean定義的註冊類,通過這個類可以註冊、查詢、移除IOC容器中的bean
        BeanDefinitionRegistry registry = conditionContext.getRegistry();
        */
        //3. 獲取運行時的環境信息
        Environment environment = conditionContext.getEnvironment();
         //獲取當前的操作系統名字
        String osName = environment.getProperty("os.name");
      
        //判斷當前操作系統是不是Windows 10
        if(osName.contains("Windows")){
            return true;
        }

        return false;
    }
}

1.7 @Import

給容器中註冊組件(bean),前面已經寫了兩種:

  • 包掃描+組件標註註解(@Service / @Controller / @Repository / @Componment),這種方式比較侷限,只能用在自己寫的類上
  • @Bean 可以註冊第三方包裏面的組件,使用類的構造函數完成

@Import(類名.class) 快速給容器中導入一個組件,id爲類的全路徑名,用在類上,屬性爲class類型的數組
比如,在容器中註冊Color、Red類

@Configuration
//@Import:快速在容器中註冊組件,id爲類的全路徑名
@Import({Color.class, Red.class})
public class MainConfig02 {
	。。。}
  • @Import中,還可以寫一個選擇器ImportSelector。選擇器需要自己寫一個類,實現ImportSelector接口,實現selectImports方法,這個方法返回一個String的數組,數組中就是要註冊的bean的全路徑名。
@Configuration
/* @Import:快速在容器中註冊組件,id爲類的全路徑名
       類名.class
       選擇器類.class
 */
@Import({Color.class, Red.class, MyImportSelector.class})
public class MainConfig02 {
	。。。}

自定義選擇器類

//選擇器類
public class MyImportSelector implements ImportSelector {
    /**
     * @param importingClassMetadata:可以獲得@Import標註的類的所有註解信息
     * @return 返回一個String數組,裏面要註冊的bean的全路徑名,id是全路徑名
     */
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{"ewen.bean.Blue"};
    }
}

  • @Import 中還可以寫一個註冊器類,該類實現ImportBeanDefinitionRegistrar接口,實現registerBeanDefinitions方法,在方法中可以手工註冊bean,並且設置bean的id。
@Configuration
/* @Import:快速在容器中註冊組件,id爲類的全路徑名
       類名.class
       選擇器類.class:實現ImportSelector接口的類
       註冊類.class:實現ImportBeanDefinitionRegistrar接口的類
 */
@Import({Color.class, Red.class, MyImportSelector.class, MyImportBeanDefinitionRegistrar.class})
public class MainConfig02 {
	。。。}

註冊類

//註冊類
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    /**
     AnnotationMetadata:@Import標註類的註解信息
     BeanDefinitionRegistry:bean的註冊類,通過這個類,調用registerBeanDefinition方法
                            可以手動的在容器中註冊bean。也可以用這個類中的方法進行查詢、移除bean
     */
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        //先判斷IOC容器中是否有藍色和紅色,如果都有,就註冊一個RainBow
        boolean b1 = registry.containsBeanDefinition("ewen.bean.Blue");
        boolean b2 = registry.containsBeanDefinition("ewen.bean.Red");
        if(b1 && b2){
            //指定bean的定義信息(bean的類型、作用域等等)
            RootBeanDefinition beanDefinition = new RootBeanDefinition(RainBow.class);
            //指定bean的id
            registry.registerBeanDefinition("rainBow",beanDefinition);
        }

    }
}

1.8 FactoryBean

使用spring提供的FactoryBean(工廠bean)也可以給容器註冊bean。FactoryBean 是一個接口,裏面有三個方法,getObject,獲取對象,getObjectType,獲取對象類型,isSingleton,是否是單例模式。定義一個類實現FactoryBean接口,實現上面三個方法,然後在配置類中,使用@Bean註解來註冊這個類,需要注意的是,通過getBean方法獲取的是在getObject中生成的對象,而不是FactoryBean對象,可以在name前面加一個“&”獲取FactoryBean 對象。

//自定義的FactoryBean,通過這個FactoryBean來給容器註冊Bean
public class ColorFactoryBean implements FactoryBean<Color> {
    public Color getObject() throws Exception {
        return new Color();
    }

    public Class<?> getObjectType() {
        return Color.class;
    }
    //返回true,是單例,返回false不是單例
    public boolean isSingleton() {
        return true;
    }
}

配置類

 @Bean
 public ColorFactoryBean colorFactoryBean(){
      return new ColorFactoryBean();
  }

2. Bean 的聲明週期

bean創建—初始化—銷燬的過程

  • bean的創建:單實例:在容器啓動的時候創建對象。多實例:在每次獲取的時候創建對象。
  • 初始化:對象創建完成,並且賦值好,調用初始化方法。
  • 銷燬:單實例:容器關閉的時候。多實例:容器不會管理這個bean,容器不會調用銷燬的方法。

2.1 容器管理bean的生命週期

我們可以自定義初始化和銷燬方法,容器在bean進行到當前生命週期的時候來調用自定義的初始化和銷燬方法。
指定bean的初始化和銷燬方法

  • 通過@Bean註解,指定initMethod和destoryMethod 屬性
  • 通過讓Bean實現InitializingBean(定義初始化邏輯),DisposableBean(定義銷燬邏輯)接口;
  • 使用@PostConstruct:在bean創建完成並且屬性賦值完成,來執行初始化方法
    @PreDestroy:在容器銷燬bean之前通知我們進行清理工作
  • BeanPostProcessor【interface】:bean的後置處理器,在bean初始化前後進行一些處理工作,需要定義一個類實現該接口,實現下面兩個方法
    postProcessorBeforeInitialization:在初始化之前工作
    postProcessAfterInitialization:在初始化之後工作

BeanPostProcessor 原理

  • 調用populateBean方法給bean賦好值,執行initializeBean方法
  • nitializeBean方法中主要執行applyBeanPostProcessorsBeforeInitialization 遍歷得到容器中所有的BeanPostProcessor;挨個執行beforeInitialization,一但返回null,跳出for循環,不會執行後面的postProcessBeforeInitialization方法
  • 執行完applyBeanPostProcessorsBeforeInitialization ,執行初始化方法,再執行applyBeanPostProcessorsAfterInitialization方法

Spring底層對 BeanPostProcessor 的使用
bean賦值,注入其他組件,@Autowired,生命週期註解功能,@Async等都使用的是xxx BeanPostProcessor。

3. 屬性注入相關注解

3.1 @Value

xml中是在標籤下使用進行屬性注入的,可以使用@Value註解代替,@Value中,可以寫:

  • 普通字符串
  • SpEL(Spring Expression Language):#{ },裏面可以進行運算
  • 使用${}從配置文件中獲取,必須在配置類中使用@PropertySource註解讀取外部配置文件的k/v並保存到運行的環境變量中

3.2 @Autowired

自動注入,默認優先根據類型在容器中找對應的組件,將組件賦給@Autowired下的屬性,如果容器中有多個相同類型的組件,會將屬性名作爲id在容器中查找組件。

  • @Qualifier:使用@Qualifier註解來指定,要注入的bean的id,而不是使用屬性值作爲id
  • @Primary:使用@Primary來指定首選的bean,這樣,spring自動注入的時候,會將首選的bean注入

如果容器中沒有相同類型的bean或者指定id的bean,會報錯,可以修改required屬性爲false(默認爲true),就不報錯了。
@Autowired 還可以用在構造器、方法、參數上,參數默認從容器中獲取。

3.3 @Resouce 和 @Inject

@Resouce 和 @Inject 都可以自動注入,@Resource(name="") 是根據bean的id自動注入的,不支持@Primary來指定首選bean。使用@Inject 要導入javax.inject包,註解裏沒有屬性。這兩個註解式java規範的,@Autowired是spring定義的。

3.4 XXXAware

自定義組件想要使用Spring容器底層的一些組件(ApplicationContext,BeanFactory,xxx),自定義組件類可以實現xxxAware;在創建對象的時候,會調用接口規定的方法注入相關組件;Aware,把Spring底層一些組件注入到自定義的Bean中;
xxxAware:底層使用xxxAwareProcessor, 如ApplicationContextAware ==》ApplicationContextAwareProcessor。在bean中獲取applicationContext。

//實現ApplicationContextAware接口,可以在Color類中獲得applicationContext
//實現BeanNameAware接口,可以獲得bean的id
//實現EmbeddedValueResolverAware接口,可以解析字符串
@Component
public class Color implements ApplicationContextAware, BeanNameAware,EmbeddedValueResolverAware {
    private ApplicationContext applicationContext;
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
    public void setBeanName(String name) {
        System.out.println("bean 的name:" + name);
    }

    //StringValueResolver解析字符串
    public void setEmbeddedValueResolver(StringValueResolver resolver) {
        String s = resolver.resolveStringValue("你好${os.name} #{18 * 20}");
        System.out.println("解析的字符串:" + s);
    }

3.5 @Profile

指定組件在哪個環境的情況下才能被註冊到容器中,不指定,任何環境下都能註冊這個組件
比如,在tes環境下,將dataSourceTest註冊到容器,開發dev環境下,將dataSourceDev註冊到容器

參考:
Spring註解驅動教程

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章