Spring註解驅動開發(一)IOC和DI

1、背景介紹

       隨着現在SpringBoot的越來越流行,Spring的註解開發就應該得到重視。因爲SpringBoot中很多的註解,對於之前進行xml配置進行開發的人來說,會顯得比較陌生。而SpringBoot其實就是更好的封裝了Spring,是基於Spring的。所以瞭解Spring的註解開發,對於學習SpringBoot有很好的幫助,並且Spring的註解開發可以提高開發效率,可以免除繁雜的xml配置的煩惱。

2、註解開發實現IOC和DI

2.1 創建Maven工程

1、創建maven工程

File——》Project——》選擇Maven:

下一步如下:

點擊finish即可。

2、添加對應的依賴

在pom.xml文件中,添加Spring對應的座標,以及junit座標:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.spring.annotation</groupId>
    <artifactId>spring-annotation</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.2.4.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project>

2.2 IOC

       以前我們的使用xml配置文件applicatonContext.xml來管理Spring的Bean,現在我們不再需要xml的配置文件,通過Spring的相關注解就能實現。

2.2.1 IOC的入門案例

1、創建pojo

創建一個Bean,該Bean需要交給Spring進行管理。

public class User {
    private String name;
    private int age;
    //get/set方法,有參無參構造,以及toString方法
}

2、創建配置類

       創建一個Spring的配置類,這個配置類使用註解:@Configuration  修飾,表明該類是Spring的配置類,相當於是一個applicationContext.xml配置文件,裏面可以定義Spring管理的Bean,定義Bean的時候要使用註解:@Bean

package com.spring.annotation.config;

import com.spring.annotation.bean.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @Configuration:表示這個類是配置類,相當於配置文件開發的時候的applicationContext.xml文件
 */
@Configuration
public class SpringConfig {

    /**
     * @Bean:給容器中註冊一個Bean;類型爲返回值的類型,id默認是用:方法名作爲id
     *         也可以使用value屬性來爲它指定一個 id
     */
    @Bean(value = "user")
    public User user(){
        return new User("張三",23);
    }

}

3、測試類,從Spring的配置類中獲取實例對象

package com.spring.annotation.test;

import com.spring.annotation.bean.User;
import com.spring.annotation.config.SpringConfig;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class SpringConfigTest {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
        User user = applicationContext.getBean(User.class);
        System.out.println(user);
        String[] names = applicationContext.getBeanNamesForType(User.class);
        for(String name : names){
            System.out.println(name);
        }
    }
}

      注意:我們使用的是 AnnotationConfigApplicationContext 對象來獲取 ApplicationContext (IOC容器對象),xml配置開發的時候,使用的是:ClassPathXmlApplicationContext。

打印結果:

User{name='張三', age=23}
user

       以上通過簡單的幾步,就實現了把Bean交給Spring管理,IOC的功能就已經得到了實現,只是使用了幾個簡單的註解,根本就不需要複雜的xml配置,這樣很明顯能提高我們的開發效率。

2.2.2 IOC相關的其他註解

1、@ComponentScans和@ComponentScan

        @ComponentScans 註解相當於xml配置文件中的標籤:<context:component-scan base-package="com.springmvc,com.test" />,在標籤中可以配置掃描多個包,包名之間用逗號分隔,同理,在@ComponentScans中可以配置多個@ComponentScan(在JDK8以後,可以在配置類上配置同級的多個@ComponentScan 也是同樣的效果)。

useDefaultFilters=false:不使用默認的掃描規則(默認是掃描所有@Component和@Controller @Service、@Repository註解)
includeFilters:配置只掃描哪些
excludeFilters:配置不掃描哪些

a、在SpringConfig類上添加@ComponentScans和@ComponentScan註解:

package com.spring.annotation.config;

import com.spring.annotation.bean.User;
import org.springframework.context.annotation.*;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Service;

/**
 * @Configuration:表示這個類是配置類,相當於配置文件開發的時候的applicationContext.xml文件
 */
@Configuration
/**
 *  @ComponentScans 相當於標籤:<context:component-scan base-package="com.springmvc,com.test" />
 *                  useDefaultFilters=false:不使用默認的掃描規則(默認是掃描所有@Component和@Controller
 *                                             @Service、@Repository註解)
 *                  includeFilters:配置只掃描哪些
 *                  excludeFilters:配置不掃描哪些
 */
@ComponentScans(value = {
        @ComponentScan(value = "com.spring.annotation",
                       includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class, Service.class})},
                       useDefaultFilters = false),
        @ComponentScan(value = "com.spring.test",
                       excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class, Service.class})}
                       )
                       }
               )
public class SpringConfig {

    /**
     * @Bean:給容器中註冊一個Bean;類型爲返回值的類型,id默認是用:方法名作爲id
     *         也可以使用value屬性來爲它指定一個 id
     */
    @Bean(value = "user")
    public User user(){
        return new User("張三",23);
    }

}

b、創建UserController,類上添加@Controller註解,創建UserService,類上添加@Service註解,創建UserDao,類上添加@Repository註解。

c、在src/test下面創建測試類,測試Spring掃描了哪些類:

package com.spring.annotation.test;

import com.spring.annotation.config.SpringConfig;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class IOCTest {

    @Test
    public void test(){
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        for(String beanName : beanDefinitionNames){
            System.out.println(beanName);
        }
    }
}
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalRequiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
springConfig
userController
userService
user

並沒有掃描道UserDao,因爲我們指定了只掃描@Controller和@Service。

FilterType.ANNOTATION:按照註解

FilterType.ASSIGNABLE_TYPE:按照給定的類型;

@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,classes = UserDao.class)

FilterType.ASPECTJ:使用ASPECTJ表達式

FilterType.REGEX:使用正則指定

FilterType.CUSTOM:使用自定義規則

2、@Scope註解

可以在創建Bean實例的方法上使用@Scope,指定作用域,默認是單實例的:singleton:

    @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
    @Bean(value = "user")
    public User user(){
        return new User("張三",23);
    }
可取值以及解釋:
@see ConfigurableBeanFactory#COPE_PROTOTYPE
@see ConfigurableBeanFactory#SCOPE_SINGLETON
@see org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST  request
@see org.springframework.web.context.WebApplicationContext#SCOPE_SESSION    sesssio
prototype:多實例的:ioc容器啓動並不會去調用方法創建對象放在容器中。每次獲取的時候纔會調用方法創建對象;
singleton:單實例的(默認值):ioc容器啓動會調用方法創建對象放到ioc容器中。以後每次獲取就是直接從容器(map.get())中拿,
request:同一次請求創建一個實例
session:同一個session創建一個實例

3、@Lazy註解

       @Lazy註解可以實現懶加載;
       懶加載只針對單實例bean(Scope作用域是singleton的):默認在容器啓動的時候創建對象;
       懶加載:容器啓動不創建對象。第一次使用(獲取)Bean創建對象,並初始化;第二次獲取的也是同一個對象,因爲是單實例的。這其實就是單例模式的懶漢式和餓漢式

4、@Conditional註解

       @Conditional註解在SpringBoot中被大量的使用,條件化註冊Bean實例。

       @Conditional({Condition}) : 該註解接受一個Condition(是一個接口)的數組,按照一定的條件進行判斷,滿足條件纔會給容器中註冊bean。如果不滿足Condition條件,便不會創建@Conditional註解修飾的Bean。

       需求:當前環境是Linux系統,就創建Linux之父的User對象linus,如果是windows環境,就創建windows之父的User對象Bill Gates。

步驟:

第一步:創建兩個Bean,分別返回linus的User對象和Bill Gates的User對象,但是創建Bean的方法上要加上@Conditional註解,註解裏面分別添加自定義的的Condition接口的實現類:LinuxConditiion和WindowsCondition。

 @Conditional(LinuxCondition.class)
    @Bean
    public User linus(){
        return new User("linus",50);
    }

    @Conditional(WindowsCondition.class)
    @Bean
    public User bill(){
        return new User("bill gates",65);
    }

第二步:定義Condition接口的實現類:LinuxConditiion和WindowsCondition,通過Condition接口的方法來獲取當前系統的環境名稱,然後返回true;

package com.spring.annotation.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;

//判斷是否linux系統
public class LinuxCondition implements Condition {

    /**
     * ConditionContext:判斷條件能使用的上下文(環境)
     * AnnotatedTypeMetadata:註釋信息
     */
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata annotatedTypeMetadata) {
        //1、能獲取到ioc使用的beanfactory,BeanFactory的有創建和注入Bean的信息
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        //2、獲取類加載器
        ClassLoader classLoader = context.getClassLoader();
        //3、獲取當前環境信息
        Environment environment = context.getEnvironment();
        //4、獲取到bean定義的註冊類
        BeanDefinitionRegistry registry = context.getRegistry();
        //獲取當前環境的操作系統的名稱
        String osName = environment.getProperty("os.name");

        //可以判斷容器中的bean註冊情況,也可以給容器中註冊bean
        boolean definition = registry.containsBeanDefinition("person");
        if(osName.contains("linux")){
            return true;
        }
        return false;
    }
}
package com.spring.annotation.condition;

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;
//判斷是否是windows操作系統
public class WindowsCondition implements Condition {

    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        Environment environment = context.getEnvironment();
        String property = environment.getProperty("os.name");
        if(property.contains("Windows")){
            return true;
        }
        return false;
    }
}

第三步:測試

 @Test
    public void test2(){
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
        ConfigurableEnvironment environment = (ConfigurableEnvironment) applicationContext.getEnvironment();
        //動態獲取環境變量的值;Windows 10
        String property = environment.getProperty("os.name");
        System.out.println(property);

        String[] namesForType = applicationContext.getBeanNamesForType(User.class);
        for (String name : namesForType) {
            System.out.println(name);
        }

        Map<String, User> persons = applicationContext.getBeansOfType(User.class);
        System.out.println(persons);
    }

在windows環境下,執行測試方法,打印:

Windows 10
user
bill
{user=User{name='張三', age=23}, bill=User{name='bill gates', age=65}}

IDEA修改VM參數,模擬Linux環境:Edit Configuration,添加:-Dos.name=linux

再次執行,結果打印如下:

linux
user
linus
{user=User{name='張三', age=23}, linus=User{name='linus', age=50}}

注意:@Conditional({WindowsCondition.class}) 也可以放到類上面,類中組件統一設置。滿足當前條件,這個類中配置的所有bean註冊才能生效;

5、@Import註解

之前我們已經學過兩種把Bean註冊到IOC容器的方法:

方式一:包掃描+組件標註註解(@Controller/@Service/@Repository/@Component)[自己寫的類],但是這樣只能註冊自己寫的類,如果是第三方jar包裏面的類,我們就無法添加註解了,就需要使用方式二。

方式二:@Bean[導入的第三方包裏面的組件],比如我們需要導入JdbcTemplate,就可以使用@Bean註解進行註冊。

其實@Import也是向IOC容器註冊組件的,它可以快速給容器中導入一個組件,它的使用方式有如下三種:

5.1 @Import(要導入到容器中的組件的Class對象)

在配置類(比如之前的SpringConfig)上面添加@Import註解,容器中就會自動註冊這個組件,id默認是全類名。

定義個新的pojo:

public class Car {
}

在SpringConfig配置類上添加@Import註解:

@Import({Car.class})
public class SpringConfig {
    //。。。省略了其他註冊Bean的方法
}

測試方法,打印IOC容器中所有的Bean:

@Test
    public void testImport(){
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
        //獲取IOC容器中所有註冊好的Bean
        String[] definitionNames = applicationContext.getBeanDefinitionNames();
        for (String name : definitionNames) {
            System.out.println(name);
        }
    }

5.2 ImportSelector

       可以定義一個實現接口ImportSelector接口的實現類,自定義導入註解的規則,然後把該實現類的Class對象也放到@Import註解中。ImportSelector:返回需要導入的組件的全類名數組

第一步,創建兩個類Audi和Jili(在包com.spring.annotation.bean下面創建)

public class Car {
}

public class Jili {
}

第二步,自定義MyImportSelector

package com.spring.annotation.condition;

import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;

//自定義邏輯返回需要導入的組件
public class MyImportSelector implements ImportSelector {
	//返回值,就是到導入到容器中的組件全類名
	//AnnotationMetadata:當前標註了@Import註解的類(比如SpringConfig)的所有註解信息
	public String[] selectImports(AnnotationMetadata importingClassMetadata) {
		//importingClassMetadata
		//方法不要返回null值,返回null會直接報空指針異常
		return new String[]{"com.spring.annotation.bean.Audi","com.spring.annotation.bean.Jili"};
	}
}

第三步,把MyImportSelector的Class對象添加到SpringConfig配置類的@Import註解上:

@Import({Car.class, MyImportSelector.class})
public class SpringConfig {
}

第四步,執行上面的測試方法,查看結果:

ImportSelector 的這種方式,在SpringBoot中使用的比較多。

5.3 ImportBeanDefinitionRegistrar

ImportBeanDefinitionRegistrar可以手動註冊bean到容器中:

第一步,創建類Dazhong

package com.spring.annotation.bean;

public class Dazhong {
}

第二步,自定義MyImportBeanDefinitionRegistrar,實現ImportBeanDefinitionRegistrar接口

package com.spring.annotation.condition;

import com.spring.annotation.bean.Dazhong;
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手工註冊進來
     */
    public void registerBeanDefinitions(AnnotationMetadata annotationMetadata,
                                        BeanDefinitionRegistry beanDefinitionRegistry) {
        boolean definition = beanDefinitionRegistry.containsBeanDefinition("com.spring.annotation.bean.Audi");
        boolean definition2 = beanDefinitionRegistry.containsBeanDefinition("com.spring.annotation.bean.Jili");
        if(definition && definition2){
            //指定Bean定義信息;(Bean的類型,Bean。。。)
            RootBeanDefinition beanDefinition = new RootBeanDefinition(Dazhong.class);
            //註冊一個Bean,指定bean名
            beanDefinitionRegistry.registerBeanDefinition("dazhong", beanDefinition);
        }
    }
}

第三步,在SpringConfig配置類的@Import註解上加上MyImportBeanDefinitionRegistrar.class

@Import({Car.class, MyImportSelector.class, MyImportBeanDefinitionRegistrar.class})
public class SpringConfig {
}

第四步測試:

6、通過FactoryBean註冊組件

       之前註冊Bean實例到IOC容器的方式,註冊的都是普通Bean,普通Bean得到的就是Bean本身,而FactoryBean註冊的getObject方法返回值的對象。

6.1 自定義CarFactoryBean

package com.spring.annotation.bean;

import org.springframework.beans.factory.FactoryBean;

public class CarFactoryBean implements FactoryBean {
    //返回一個Car對象,這個對象會添加到IOC容器中
    public Object getObject() throws Exception {
        return new Car();
    }

    public Class<?> getObjectType() {
        return Car.class;
    }

    //true:這個bean是單實例,在容器中保存一份
    //false:多實例,每次獲取都會創建一個新的bean;
    public boolean isSingleton() {
        return true;
    }
}

6.2 在SpringConfig配置類中註冊CarFacotryBean

 @Bean
    public CarFactoryBean carFactoryBean(){
        return new CarFactoryBean();
    }

6.3 測試

/**
     * 4)、使用Spring提供的 FactoryBean(工廠Bean);
     * 		1)、默認獲取到的是工廠bean調用getObject創建的對象
     * 		2)、要獲取工廠Bean本身,我們需要給id前面加一個&&colorFactoryBean
     */
    @Test
    public void testFactoryBean(){
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
        //工廠Bean獲取的是調用getObject創建的對象
        Object bean2 = applicationContext.getBean("carFactoryBean");
        Object bean3 = applicationContext.getBean("carFactoryBean");
        System.out.println("bean的類型:"+bean2.getClass());
        System.out.println(bean2 == bean3);
        //如果要獲取到真正的FactoryBean對象,要使用 &
        Object bean4 = applicationContext.getBean("&carFactoryBean");
        System.out.println(bean4.getClass());
    }

2.2.3 Spring中Bean的生命週期

       Spring管理的Bean的生命週期,就是Bean從創建,初始化再到銷燬的過程。我們可以自定義初始化和銷燬方法;容器在bean進行到當前生命週期的時候來調用我們自定義的初始化和銷燬方法。可以有如下3種方式來爲我們的Bean指定初始化和銷燬方法:

方式一:在@Bean註解上執行initMethod和destoryMethod

1、在Car類中定義初始化和銷燬方法

package com.spring.annotation.bean;

public class Car {
    public Car() {
        System.out.println("Car對象創建了");
    }
    //初始化方法,該方法不能有參數
    public void init(){
        System.out.println("init method...");
    }
    //初始化方法,該方法不能有參數
    public void destroy(){
        System.out.println("destroy method...");
    }
}

2、創建新的配置類,在配置類中註冊Car

package com.spring.annotation.config;

import com.spring.annotation.bean.Car;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SpringLifeConfig {

    @Bean(initMethod="init",destroyMethod="destroy")
    public Car car(){
        return new Car();
    }
}

3、測試

package com.spring.annotation.test;

import com.spring.annotation.config.SpringLifeConfig;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class SpringLifeTest {

    @Test
    public void testLife(){
        AnnotationConfigApplicationContext applicationContext =
                            new AnnotationConfigApplicationContext(SpringLifeConfig.class);
        System.out.println("IOC容器創建完成...");

        //關閉容器
        applicationContext.close();
    }
}

注意:

構造(對象創建)
        單實例:在容器啓動的時候創建對象
        多實例:在每次獲取的時候創建對象

初始化:
        對象創建完成,並賦值好,調用初始化方法。。。

銷燬:
        單實例:容器關閉的時候
        多實例:容器不會管理這個bean;容器不會調用銷燬方法;可以手動調用銷燬方法

方式二:讓自定義的Bean實現InitializingBean(定義初始化邏輯),DisposableBean(定義銷燬邏輯)

package com.spring.annotation.bean;

import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;

@Component
public class Person implements InitializingBean, DisposableBean {
    public void destroy() throws Exception {
        System.out.println("DisposableBean --- 銷燬方法");
    }

    public void afterPropertiesSet() throws Exception {
        System.out.println("InitializingBean --- 初始化方法");
    }

    public Person() {
        System.out.println("Person構造方法執行,創建完成");
    }
}

       類上添加了註解:@Component,交給Spring管理。 

       在SpringLifeConfig配置類上加上:@ComponentScan("com.spring.annotation.bean")//配置包掃描,會自動掃描到下面的所有的類,並註冊到IOC容器中,測試效果如下:

方式三:可以使用JSR250的兩個註解

       @PostConstruct:在bean創建完成並且屬性賦值完成;來執行初始化方法
       @PreDestroy:在容器銷燬bean之前通知我們進行清理工作

package com.spring.annotation.bean;

import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

@Component
public class Dog {
    public Dog() {
        System.out.println("Dog構造完成");
    }
    @PostConstruct
    public void postCOnstruct(){
        System.out.println("在對象創建完成後會調用該方法。。。");
    }

    @PreDestroy
    public void preDestory(){
        System.out.println("Dog的銷燬方法");
    }
}

2.2.4 BeanPostProcessor【interface】:bean的後置處理器

在bean初始化前後進行一些處理工作,針對所有的IOC容器中的Bean:
postProcessBeforeInitialization:在初始化之前工作
postProcessAfterInitialization:在初始化之後工作

1、BeanPostProcessor的使用

自定義後置處理器:添加@Component註解,交給Spring管理

package com.spring.annotation.bean;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;

@Component//交給Spring管理
public class MyBeanPostProcessor implements BeanPostProcessor {
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("postProcessBeforeInitialization..."+beanName+"=>"+bean);
        return bean;
    }

    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("postProcessAfterInitialization..."+beanName+"=>"+bean);
        return bean;
    }
}

此時再執行上面的測試方法,查看控制檯的打印:

2、BeanPostProcessor的原理

源碼中:populateBean(beanName, mbd, instanceWrapper);給bean進行屬性賦值

initializeBean()方法中:
{
applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
invokeInitMethods(beanName, wrappedBean, mbd);執行自定義初始化
applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}

Spring會遍歷得到IOC容器中所有的BeanPostProcessor;挨個執行beforeInitialization,一但返回null,跳出for循環,不會執行後面的BeanPostProcessor.postProcessorsBeforeInitialization

Spring底層對 BeanPostProcessor 的使用;bean賦值,注入其他組件,@AutowiredAnnotationBeanPostProcessor,生命週期註解功能,@AsyncAnnotationBeanPostProcessor;

2.2.5 與屬性相關注解

1、@Value

       @Value註解的作用就相當於xml配置文件中,配置<bean>標籤裏面的 value屬性。@Value註解的value屬性可以有以下三種方式賦值:

@Value(vaue="張三")   直接寫字面(數字,字符串等)值

@Value(value="#{20+2}")   可以使用SpEL表達式

@Value(value="${key}")    可以從外部配置文件中獲取值

a、創建Employee

package com.spring.annotation.bean;

import org.springframework.beans.factory.annotation.Value;

public class Employee {
    @Value("張三")
    private String name;
    @Value("#{6000+3000}")
    private int salary;
    @Value("${employee.addr}")
    private String addr;
    //get/set以及無參有參構造和toString方法
}

b、創建配置類

package com.spring.annotation.config;

import com.spring.annotation.bean.Employee;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

@Configuration
//使用@PropertySource 引入類路下面的 employee.properties 文件
@PropertySource(value = {"classpath:employee.properties"})
public class SpringPropertyConfig {

    @Bean
    public Employee employee(){
        return new Employee();
    }
}

使用@PropertySource 引入類路下面的 employee.properties 文件
@PropertySource(value = {"classpath:employee.properties"})

c、在類路徑下創建employee.properties

employee.addr=湖南長沙

d、測試類

package com.spring.annotation.test;

import com.spring.annotation.bean.Employee;
import com.spring.annotation.config.SpringLifeConfig;
import com.spring.annotation.config.SpringPropertyConfig;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;

public class SpringPropertyTest {
    @Test
    public void testProperty(){
        AnnotationConfigApplicationContext applicationContext =
                            new AnnotationConfigApplicationContext(SpringPropertyConfig.class);
        Employee employee = (Employee) applicationContext.getBean("employee");
        System.out.println(employee);

        ConfigurableEnvironment environment = applicationContext.getEnvironment();
        String property = environment.getProperty("employee.addr");
        System.out.println(property);
    }
}

通過這兩句:

ConfigurableEnvironment environment = applicationContext.getEnvironment();
String property = environment.getProperty("employee.addr");

也可以獲取配置文件中的值。

 

2.3 DI相關注解

Spring利用依賴注入(DI),完成對IOC容器中中各個組件的依賴關係賦值;完成自動裝配。

2.3.1 @Autowired

       默認優先按照類型去容器中找對應的組件:applicationContext.getBean(BookDao.class);找到就賦值;

       如果找到多個相同類型(要保證每個組件的名稱不一樣,否則就報錯)的組件,再將屬性的名稱作爲組件的id去容器中查找;

       可以配合註解@Qualifier("bookDao")一起使用,可以在注入組件的時候,指定需要裝配的組件的id,而不是使用屬性名。

       默認情況下,使用了@Autowired註解進行自動裝配,默認一定要將組件注入進來,沒有找到就會報錯。可以使用@Autowired(required=false);  指定該組件不是必須要注入的,那就不會報錯了;

       可以配合註解@Primary:讓Spring進行自動裝配的時候,默認使用首選的bean(沒有明確指定使用哪個Bean的時候);同時,也可以繼續使用@Qualifier指定需要裝配的bean的名字,他的優先級高於@Primary;

       @Autowired:可以在 構造器,參數,方法,屬性 上使用該註解;都是從容器中獲取參數組件的值。

               [標註在方法位置]:@Bean+方法參數;參數從容器中獲取,默認不寫@Autowired效果是一樣的;都能自動裝配;

@Autowired 
//標註在方法,Spring容器創建當前對象,就會調用方法,完成賦值;
//方法使用的參數,自定義類型的值從ioc容器中獲取
public void setCar(Car car) {
	this.car = car;
}

               [標在構造器上]:如果組件只有一個有參構造器,這個有參構造器的@Autowired可以省略,參數位置的組件還是可以自動從容器中獲取;(默認加在ioc容器中的組件,容器啓動會調用無參構造器創建對象,再進行初始化賦值等操作)

//構造器要用的組件,都是從容器中獲取
//@Autowired可以省略
public Boss(Car car){
	this.car = car;
	System.out.println("Boss...有參構造器");
}

               [放在參數位置]:@Bean註解標註的方法,在創建對象的時候,方法參數的值也是從容器中獲取的,在參數前面可以使用@Autowired註解,也可以省略。

/**
	 * @Bean標註的方法創建對象的時候,方法參數的值從容器中獲取
	 * @param car
	 * @return
	 */
	@Bean
	public Color color(Car car){
		Color color = new Color();
		color.setCar(car);
		return color;
	}

2.3.2 @Resource(JSR250)和@Inject(JSR330)[都java規範的註解]

@Resource:
            可以和@Autowired一樣實現自動裝配功能;默認是按照組件名稱進行裝配的;
            不能支持@Primary功能,不支持@Autowired(reqiured=false);

@Inject:
            需要導入javax.inject的包,和Autowired的功能一樣。沒有required=false的功能;

@Autowired:Spring定義的; @Resource、@Inject都是java規範

2.3.3 自定義組件中注入Spring容器底層的組件

       自定義組件想要使用Spring容器底層的一些組件(例如:ApplicationContext,BeanFactory,xxx);只需要讓 自定義組件實現xxxAware;在創建對象的時候,會調用接口規定的方法注入相關組件;Aware;

       把Spring底層一些組件注入到自定義的Bean中:Bean實現xxxAware:使用xxxProcessor功能;比如,如果在我們自定義的組件中,需要使用ApplicationContext 容器對象,那麼就讓Bean實現ApplicationContextAware接口,重寫接口中的方法,Spring就會通過對應的 ApplicationContextAwareProcessor(後置處理器)把我們需要的組件注入到自定義組件中。自定義的組件就可以使用一個變量來存儲並使用他。

package com.atguigu.bean;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.EmbeddedValueResolverAware;
import org.springframework.stereotype.Component;
import org.springframework.util.StringValueResolver;

@Component
public class Red implements ApplicationContextAware,BeanNameAware,EmbeddedValueResolverAware {
	
	private ApplicationContext applicationContext;

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		System.out.println("傳入的ioc:"+applicationContext);
		this.applicationContext = applicationContext;
	}

	@Override
	public void setBeanName(String name) {
		System.out.println("當前bean的名字:"+name);
	}

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

}

2.4 動態加載環境@Profile

       @Profile:是Spring爲我們提供的可以根據當前環境,動態的激活和切換一系列組件的功能。

       開發,測試和生產環境存在不同,需要程序可以動態的去加載不同的配置,比如數據源的不同,爲了解決這個問題,Spring提供了@Profile註解,可以根據當前配置,動態的激活不同的組件。

       @Profile:指定組件在哪個環境的情況下才能被註冊到容器中,不指定,任何環境下都能註冊這個組件

  * 1)、加了環境標識的bean,只有這個環境被激活的時候才能註冊到容器中。默認是default環境。
  * 2)、寫在配置類上,只有是指定的環境的時候,整個配置類裏面的所有配置才能開始生效
  * 3)、沒有標註環境標識的bean在,任何環境下都是加載的;

代碼演示:

數據資源文件dbconfig.properties

db.user=root
db.password=123456
db.driverClass=com.mysql.jdbc.Driver

涉及到數據源,需要引入下面的連接池和數據庫驅動的jar包:

<!-- https://mvnrepository.com/artifact/c3p0/c3p0 -->
		<dependency>
			<groupId>c3p0</groupId>
			<artifactId>c3p0</artifactId>
			<version>0.9.1.2</version>
		</dependency>

		<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.1.44</version>
		</dependency>

 

配置類如下:

package com.spring.annotation.config;


import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.EmbeddedValueResolverAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.context.annotation.PropertySource;
import org.springframework.util.StringValueResolver;

import com.mchange.v2.c3p0.ComboPooledDataSource;

/**
 * Profile:
 * 		Spring爲我們提供的可以根據當前環境,動態的激活和切換一系列組件的功能;
 * 
 * 開發環境、測試環境、生產環境;
 * 數據源:(/A)(/B)(/C);
 * 
 * 
 * @Profile:指定組件在哪個環境的情況下才能被註冊到容器中,不指定,任何環境下都能註冊這個組件
 * 
 * 1)、加了環境標識的bean,只有這個環境被激活的時候才能註冊到容器中。默認是default環境
 * 2)、寫在配置類上,只有是指定的環境的時候,整個配置類裏面的所有配置才能開始生效
 * 3)、沒有標註環境標識的bean在,任何環境下都是加載的;
 */
//使用@PropertySource引入類路徑下的dbconfig.properties資源文件, / 表示從根目錄開始
@PropertySource("classpath:/dbconfig.properties")
@Configuration
public class MainConfigOfProfile implements EmbeddedValueResolverAware{
	
	@Value("${db.user}")
	private String user;
	
	private StringValueResolver valueResolver;
	
	private String  driverClass;
	
	@Bean
	public Yellow yellow(){
		return new Yellow();
	}
	
	@Profile("test")
	@Bean("testDataSource")
	public DataSource dataSourceTest(@Value("${db.password}")String pwd) throws Exception{
		ComboPooledDataSource dataSource = new ComboPooledDataSource();
		dataSource.setUser(user);
		dataSource.setPassword(pwd);
		dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
		dataSource.setDriverClass(driverClass);
		return dataSource;
	}
		
	@Profile("dev")
	@Bean("devDataSource")
	public DataSource dataSourceDev(@Value("${db.password}")String pwd) throws Exception{
		ComboPooledDataSource dataSource = new ComboPooledDataSource();
		dataSource.setUser(user);
		dataSource.setPassword(pwd);
		dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/ssm_crud");
		dataSource.setDriverClass(driverClass);
		return dataSource;
	}
	
	@Profile("prod")
	@Bean("prodDataSource")
	public DataSource dataSourceProd(@Value("${db.password}")String pwd) throws Exception{
		ComboPooledDataSource dataSource = new ComboPooledDataSource();
		dataSource.setUser(user);
		dataSource.setPassword(pwd);
		dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/scw_0515");
		
		dataSource.setDriverClass(driverClass);
		return dataSource;
	}

    //實現了EmbeddedValueResolverAware接口,可以通過該方法手動註冊
	@Override
	public void setEmbeddedValueResolver(StringValueResolver resolver) {
		this.valueResolver = resolver;
            //通過值解析器直接共配置文件中解析得到驅動類
		driverClass = valueResolver.resolveStringValue("${db.driverClass}");
	}

}

測試類:

public class IOCTest_Profile {
	
	//1、使用命令行動態參數: 在虛擬機參數位置加載 -Dspring.profiles.active=test
	//2、代碼的方式激活某種環境;
	@Test
	public void test01(){
		AnnotationConfigApplicationContext applicationContext = 
				new AnnotationConfigApplicationContext();
		//1、創建一個applicationContext
		//2、設置需要激活的環境
		applicationContext.getEnvironment().setActiveProfiles("dev");
		//3、註冊主配置類
		applicationContext.register(MainConfigOfProfile.class);
		//4、啓動刷新容器
		applicationContext.refresh();
		
		
		String[] namesForType = applicationContext.getBeanNamesForType(DataSource.class);
		for (String string : namesForType) {
			System.out.println(string);
		}
		
		Yellow bean = applicationContext.getBean(Yellow.class);
		System.out.println(bean);
		applicationContext.close();
	}

}

 

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