Spring註解驅動開發(一)

簡介-註解驅動開發

當我們還在使用Spring、SpringMVC、Mybatis三大框架來整合開發的時候,我們會寫大量的xml文件來進行配置;然而在Springboot和SpringCloud興起之後,學習Spring的註解驅動及其原理那將會是非常有必要的了;因爲在Springboot和SpringCloud裏面會使用到大量的註解來進行配置;當我們熟練掌握了Spring的註解驅動,那當我們在學習Springboot和SpringCloud框架的時候,那將會更加的輕鬆自如;讓我們一起來開啓Spring註解驅動開發的學習之旅吧!

組件註冊-@Configuration&@Bean給容器中註冊組件

創建一個Maven項目:spring-annotation
導入 spring-context jar包 – 這個就是Spring核心環境所有依賴的jar包
Maven倉庫中的spring-context


<?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>com.ldc</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.3.12.RELEASE</version>
        </dependency>
    </dependencies>

</project>

  • xml文件配置的方式
    先按照我們以前配置的方式來使用Spring:
    首先有一個Person類:
public class Person {
    private String name;
    private Integer age;

    public Person() {
    }

    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

我們再寫上一個Spring的xml配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	   xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

	<bean id="person" class="com.ldc.bean.Person">
		<property name="age" value="18"></property>
		<property name="name" value="張三"></property>
	</bean>
</beans>

測試類來測試:

public class MainTest {
    public static void main(String[]args){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
        Person person = (Person) applicationContext.getBean("person");
        System.out.println(person);
    }
}

輸出結果爲:

Person{name=‘張三’, age=18}


  • 註解的方式

首先我們先寫一個配置類:等同於xml配置文件

/**
 * @Description 配置類就等同以前的配置文件
 */
@Configuration //告訴Spring這是一個配置類
public class MainConfig {

    //相當於xml配置文件中的<bean>標籤,告訴容器註冊一個bean
    //之前xml文件中<bean>標籤有bean的class類型,那麼現在註解方式的類型當然也就是返回值的類型
    //之前xml文件中<bean>標籤有bean的id,現在註解的方式默認用的是方法名來作爲bean的id
    @Bean
    public Person person() {
        return new Person("lisi",20);
    }

}

現在,我們來測試一下:

public class MainTest {
    public static void main(String[]args){
        /**
         * 這裏是new了一個AnnotationConfigApplicationContext對象,以前new的ClassPathXmlApplicationContext對象
         * 的構造函數裏面傳的是配置文件的位置,而現在AnnotationConfigApplicationContext對象的構造函數裏面傳的是
         * 配置類的類型
         */
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
        Person person = applicationContext.getBean(Person.class);
        System.out.println(person);
    }
}

測試結果如下:

Person{name=‘lisi’, age=20}


我們也可以通過ApplicationContext的一些方法來獲取容器裏面bean的一些信息,比如我們可以獲取Person這個bean在IOC容器裏面的名字,也是相當於是xml配置文件裏面標籤裏面的id屬性;

public class MainTest {
    public static void main(String[]args){
        /**
         * 這裏是new了一個AnnotationConfigApplicationContext對象,以前new的ClassPathXmlApplicationContext對象
         * 的構造函數裏面傳的是配置文件的位置,而現在AnnotationConfigApplicationContext對象的構造函數裏面傳的是
         * 配置類的類型
         */
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
        Person person = applicationContext.getBean(Person.class);
        System.out.println(person);

        //我們可以來獲取bean的定義信息
        String[] namesForType = applicationContext.getBeanNamesForType(Person.class);
        for (String name : namesForType) {
            System.out.println(name);
        }
    }
}

測試結果如下:

Person{name=‘lisi’, age=20}
person


Spring註解的方式默認是以配置的方法名來作爲這個bean的默認id,如果我們不想要方法名來作爲bean的id,我們可以在@Bean這個註解的value屬性來進行指定:

@Configuration //告訴Spring這是一個配置類
public class MainConfig {

    //相當於xml配置文件中的<bean>標籤,告訴容器註冊一個bean
    //之前xml文件中<bean>標籤有bean的class類型,那麼現在註解方式的類型當然也就是返回值的類型
    //之前xml文件中<bean>標籤有bean的id,現在註解的方式默認用的是方法名來作爲bean的id
    @Bean(value = "person")//通過這個value屬性可以指定bean在IOC容器的id
    public Person person01() {
        return new Person("lisi",20);
    }

}

我們再來運行這個測試類:

public class MainTest {
    public static void main(String[]args){
        /**
         * 這裏是new了一個AnnotationConfigApplicationContext對象,以前new的ClassPathXmlApplicationContext對象
         * 的構造函數裏面傳的是配置文件的位置,而現在AnnotationConfigApplicationContext對象的構造函數裏面傳的是
         * 配置類的類型
         */
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
        Person person = applicationContext.getBean(Person.class);
        System.out.println(person);

        //我們可以來獲取bean的定義信息
        String[] namesForType = applicationContext.getBeanNamesForType(Person.class);
        for (String name : namesForType) {
            System.out.println(name);
        }
    }
}

那麼現在的測試結果如下:bean在IOC容器的名字就是@Bean這個註解的value屬性的值,而不是默認的id是方法名person01

Person{name=‘lisi’, age=20}
person

組件註冊-@ComponentScan-自動掃描組件&指定掃描規則

在xml文件配置的方式,我們可以這樣來進行配置:

    <!-- 包掃描、只要標註了@Controller、@Service、@Repository,@Component -->
    <context:component-scan base-package="com.ldc"/>

以前是在xml配置文件裏面寫包掃描,現在我們可以在配置類裏面寫包掃描:

/**
 * @Description 配置類就等同以前的配置文件
 */
@Configuration //告訴Spring這是一個配置類
@ComponentScan(value = "com.ldc")//相當於是xml配置文件裏面的<context:component-scan base-package="com.ldc"/>
public class MainConfig {

    //相當於xml配置文件中的<bean>標籤,告訴容器註冊一個bean
    //之前xml文件中<bean>標籤有bean的class類型,那麼現在註解方式的類型當然也就是返回值的類型
    //之前xml文件中<bean>標籤有bean的id,現在註解的方式默認用的是方法名來作爲bean的id
    @Bean(value = "person")//通過這個value屬性可以指定bean在IOC容器的id
    public Person person01() {
        return new Person("lisi",20);
    }

}

我們創建BookController、BookService、BookDao這幾個類,分別添加了@Controller@Service@Repository註解:

@Controller
public class BookController {
}
@Service
public class BookService {
}
@Repository
public class BookDao {
}

我們可以引入junit的jar包來進行測試:

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

我們來進行單元測試:

    @Test
    public void test01() {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
        String[] definitionNames = applicationContext.getBeanDefinitionNames();
        for (String name : definitionNames) {
            System.out.println(name);
        }
    }

測試結果如下:除開IOC容器自己要裝配的一些組件外,還有是我們自己裝配的組件

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
mainConfig
bookController
bookDao
bookService
person

從上面的測試結果我們可以發現主配置類 MainConfig 也是IOC容器裏面的組件,也被納入了IOC容器的管理:

/**
 * @Description 配置類就等同以前的配置文件
 */
@Configuration //告訴Spring這是一個配置類
@ComponentScan(value = "com.ldc")//相當於是xml配置文件裏面的<context:component-scan base-package="com.ldc"/>
public class MainConfig {

    //相當於xml配置文件中的<bean>標籤,告訴容器註冊一個bean
    //之前xml文件中<bean>標籤有bean的class類型,那麼現在註解方式的類型當然也就是返回值的類型
    //之前xml文件中<bean>標籤有bean的id,現在註解的方式默認用的是方法名來作爲bean的id
    @Bean(value = "person")//通過這個value屬性可以指定bean在IOC容器的id
    public Person person01() {
        return new Person("lisi",20);
    }

}

我們從@Configuration 這個註解點進去就可以發現這個註解上也標註了 @Component 的這個註解,也納入到IOC容器中作爲一個組件:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {

	/**
	 * Explicitly specify the name of the Spring bean definition associated
	 * with this Configuration class. If left unspecified (the common case),
	 * a bean name will be automatically generated.
	 * <p>The custom name applies only if the Configuration class is picked up via
	 * component scanning or supplied directly to a {@link AnnotationConfigApplicationContext}.
	 * If the Configuration class is registered as a traditional XML bean definition,
	 * the name/id of the bean element will take precedence.
	 * @return the specified bean name, if any
	 * @see org.springframework.beans.factory.support.DefaultBeanNameGenerator
	 */
	String value() default "";

}

我們在 @ComponentScan 這個註解上,也是可以指定要排除哪些包或者是隻包含哪些包來進行管理:裏面傳是一個Filter[]數組
在這裏插入圖片描述
我們從這個excludeFilters方法點過去,就到了@Filter這個註解:

	@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
		 * &mdash; 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 {};

	}

這個時候,我們就可以這樣來配置:

@Configuration
@ComponentScan(value = "com.ldc",excludeFilters = {
        //這裏面是一個@Filter註解數組,FilterType.ANNOTATION表示的排除的規則 :按照註解的方式來進行排除
        //classes = {Controller.class,Service.class}表示的是標有這些註解的類給排除掉
        @Filter(type = FilterType.ANNOTATION,classes = {Controller.class,Service.class})
})
public class MainConfig {

    @Bean(value = "person")
    public Person person01() {
        return new Person("lisi",20);
    }

}

我們再來測試一下:

public class IOCTest {

    @Test
    public void test01() {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
        String[] definitionNames = applicationContext.getBeanDefinitionNames();
        for (String name : definitionNames) {
            System.out.println(name);
        }
    }

}

這個時候的測試結果如下:這個時候,bookService、bookController這兩個組件就已經被排除掉了,不再被IOC容器給管理:

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
mainConfig
bookDao
person


我們也可以來配置includeFilters:指定在掃描的時候,只需要包含哪些組件
在用xml文件配置的方式來進行配置的時候,還要禁用掉默認的配置規則,只包含哪些組件的配置才能生效

<context:component-scan base-package=“com.ldc” use-default-filters=“false”/>

在這裏插入圖片描述


這個時候,我們就可以這樣來寫:

@Configuration
//excludeFilters = Filter[];指定在掃描的時候按照什麼規則來排除腦哪些組件
//includeFilters = Filter[];指定在掃描的時候,只需要包含哪些組件
@ComponentScan(value = "com.ldc",includeFilters = {
        //這裏面是一個@Filter註解數組,FilterType.ANNOTATION表示的排除的規則 :按照註解的方式來進行排除
        //classes = {Controller.class}表示的是標有這些註解的類給納入到IOC容器中
        @Filter(type = FilterType.ANNOTATION, classes = {Controller.class})
},useDefaultFilters = false)
public class MainConfig {

    @Bean(value = "person")
    public Person person01() {
        return new Person("lisi",20);
    }

}

測試類:

public class IOCTest {

    @Test
    public void test01() {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
        String[] definitionNames = applicationContext.getBeanDefinitionNames();
        for (String name : definitionNames) {
            System.out.println(name);
        }
    }

}

這個時候,測試結果如下:這個時候是按照標有註解來進行包含的,現在就只有一個bookController被納入到IOC容器進行管理

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
mainConfig
bookController
person


@ComponentScan這個註解是可以重複定義的:來指定不同的掃描策略
在這裏插入圖片描述


我們還可以用 @ComponentScans來定義多個掃描規則:裏面是@ComponentScan規則的數組

@Configuration
//excludeFilters = Filter[];指定在掃描的時候按照什麼規則來排除腦哪些組件
//includeFilters = Filter[];指定在掃描的時候,只需要包含哪些組件
@ComponentScans(value = {
    @ComponentScan(value = "com.ldc",includeFilters = {
            //這裏面是一個@Filter註解數組,FilterType.ANNOTATION表示的排除的規則 :按照註解的方式來進行排除
            //classes = {Controller.class}表示的是標有這些註解的類給納入到IOC容器中
            @Filter(type = FilterType.ANNOTATION, classes = {Controller.class})
    },useDefaultFilters = false),
    @ComponentScan(value = "com.ldc")
})
public class MainConfig {

    @Bean(value = "person")
    public Person person01() {
        return new Person("lisi",20);
    }

}

也可以直接這樣來配置多個@ComponentScan註解:但是這樣寫的話,就必須要java8及以上的支持

@Configuration
//excludeFilters = Filter[];指定在掃描的時候按照什麼規則來排除腦哪些組件
//includeFilters = Filter[];指定在掃描的時候,只需要包含哪些組件
@ComponentScan(value = "com.ldc",includeFilters = {
        //這裏面是一個@Filter註解數組,FilterType.ANNOTATION表示的排除的規則 :按照註解的方式來進行匹配
        //classes = {Controller.class}表示的是標有這些註解的類給納入到IOC容器中
        @Filter(type = FilterType.ANNOTATION, classes = {Controller.class})
},useDefaultFilters = false)

@ComponentScan(value = "com.ldc")
public class MainConfig {

    @Bean(value = "person")
    public Person person01() {
        return new Person("lisi",20);
    }

}

組件註冊-自定義TypeFilter指定過濾規則

我們可以來看看有哪幾種過濾規則:

public enum FilterType {

	/**
	 * Filter candidates marked with a given annotation.
	 * @see org.springframework.core.type.filter.AnnotationTypeFilter
	 */
	ANNOTATION,

	/**
	 * Filter candidates assignable to a given type.
	 * @see org.springframework.core.type.filter.AssignableTypeFilter
	 */
	ASSIGNABLE_TYPE,

	/**
	 * Filter candidates matching a given AspectJ type pattern expression.
	 * @see org.springframework.core.type.filter.AspectJTypeFilter
	 */
	ASPECTJ,

	/**
	 * Filter candidates matching a given regex pattern.
	 * @see org.springframework.core.type.filter.RegexPatternTypeFilter
	 */
	REGEX,

	/** Filter candidates using a given custom
	 * {@link org.springframework.core.type.filter.TypeFilter} implementation.
	 */
	CUSTOM

}

我們可以這樣來匹配,來指定不同的匹配規則:

@Configuration
//excludeFilters = Filter[];指定在掃描的時候按照什麼規則來排除腦哪些組件
//includeFilters = Filter[];指定在掃描的時候,只需要包含哪些組件
@ComponentScans(value = {
        @ComponentScan(value = "com.ldc",includeFilters = {
                //這裏面是一個@Filter註解數組,FilterType.ANNOTATION表示的排除的規則 :按照註解的方式來進行匹配
                //classes = {Controller.class}表示的是標有這些註解的類給納入到IOC容器中

                // FilterType.ANNOTATION 按照註解來進行匹配
                // FilterType.ASSIGNABLE_TYPE 按照給定的類型來進行匹配
                @Filter(type = FilterType.ANNOTATION, classes = {Controller.class}),
                //按照給定的類型來進行匹配
                @Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {BookService.class})
        },useDefaultFilters = false)
})

public class MainConfig {

    @Bean(value = "person")
    public Person person01() {
        return new Person("lisi",20);
    }

}

測試結果如下:

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
mainConfig
bookController
bookService
person

bookService組件又重新的被IOC容器給管理了;

下面的這兩種是我們最常用的匹配規則:

FilterType.ANNOTATION 按照註解來進行匹配
FilterType.ASSIGNABLE_TYPE 按照給定的類型來進行匹配


我們還可以來寫上一個 FilterType.ASPECTJ表達式來進行匹配,這個不常用;
我們也可以按照正則表達式FilterType.REGEX的方式來進行匹配:


我們來說說最後一種:自定義匹配規則FilterType.CUSTOM

我們可以自己來寫一個匹配規則的類:MyTypeFilter,這個類要實現TypeFilter這個接口

public class MyTypeFilter implements TypeFilter {
    /**
     *
     * @param metadataReader  the metadata reader for the target class 讀取到當前正在掃描的類的信息
     * @param metadataReaderFactory a factory for obtaining metadata readers 可以獲取到其他任何類的信息
     * @return
     * @throws IOException
     */
    @Override
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
        //獲取到當前類註解的信息
        AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
        //獲取當前類的資源的信息(比如類的路徑)
        Resource resource = metadataReader.getResource();

        //獲取到當前正在掃描的類的信息
        ClassMetadata classMetadata = metadataReader.getClassMetadata();
        String className = classMetadata.getClassName();
        System.out.println("通過自定義的匹配規則--->"+className);
        return false;
    }
}

這個時候,我們就可以這樣來用了:使用FilterType.CUSTOM

@Configuration
//excludeFilters = Filter[];指定在掃描的時候按照什麼規則來排除腦哪些組件
//includeFilters = Filter[];指定在掃描的時候,只需要包含哪些組件
@ComponentScans(value = {
        @ComponentScan(value = "com.ldc",includeFilters = {
                //自定義匹配的規則
                @Filter(type = FilterType.CUSTOM, classes = {MyTypeFilter.class})
        },useDefaultFilters = false)
})

public class MainConfig {

    @Bean(value = "person")
    public Person person01() {
        return new Person("lisi",20);
    }

}

現在的測試結果如下:

通過自定義的匹配規則—>com.ldc.test.IOCTest
通過自定義的匹配規則—>com.ldc.bean.Person
通過自定義的匹配規則—>com.ldc.config.MyTypeFilter
通過自定義的匹配規則—>com.ldc.controller.BookController
通過自定義的匹配規則—>com.ldc.dao.BookDao
通過自定義的匹配規則—>com.ldc.MainTest
通過自定義的匹配規則—>com.ldc.service.BookService
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
mainConfig
person


由於,我的自定義的規則類裏面返回的是false,所有一個都沒有匹配到;
我們可以這樣來修改一下,讓clsssName裏面包含“er”的組件給匹配到:

public class MyTypeFilter implements TypeFilter {
    /**
     *
     * @param metadataReader  the metadata reader for the target class 讀取到當前正在掃描的類的信息
     * @param metadataReaderFactory a factory for obtaining metadata readers 可以獲取到其他任何類的信息
     * @return
     * @throws IOException
     */
    @Override
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
        //獲取到當前類註解的信息
        AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
        //獲取當前類的資源的信息(比如類的路徑)
        Resource resource = metadataReader.getResource();

        //獲取到當前正在掃描的類的信息
        ClassMetadata classMetadata = metadataReader.getClassMetadata();
        String className = classMetadata.getClassName();
        System.out.println("通過自定義的匹配規則--->"+className);

        if (className.contains("er")) {
            return true;
        }
        return false;
    }
}

通過自定義的匹配規則—>com.ldc.test.IOCTest
通過自定義的匹配規則—>com.ldc.bean.Person
通過自定義的匹配規則—>com.ldc.config.MyTypeFilter
通過自定義的匹配規則—>com.ldc.controller.BookController
通過自定義的匹配規則—>com.ldc.dao.BookDao
通過自定義的匹配規則—>com.ldc.MainTest
通過自定義的匹配規則—>com.ldc.service.BookService
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
mainConfig
person
myTypeFilter
bookController
bookService

這個時候,包含“er”的組件就給添加到了IOC容器中了;只要在包掃描裏面的包裏面的每一個類都會進入到這個自定義的匹配規則進行匹配;


組件註冊-@Scope-設置組件作用域

首先有一個配置類:

@Configuration
public class MainConfig2 {

    //默認是單實例的
    @Bean("person")
    public Person person() {
        return new Person();
    }

}

測試方法:

    @Test
    public void test02() {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
        String[] definitionNames = applicationContext.getBeanDefinitionNames();
        for (String name : definitionNames) {
            System.out.println(name);
        }
        //默認是單實例的
        Person person1 = (Person) applicationContext.getBean("person");
        Person person2 = (Person) applicationContext.getBean("person");
        System.out.println(person1==person2);
    }

測試結果如下:

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
mainConfig2
person
true

說明這個bean的實例是單例的;


我們可以用@Scope這個註解來指定作用域的範圍:這個就相當於在xml文件中配置的<bean>標籤裏面指定scope=“prototype” 屬性;

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Scope {

	/**
	 * Alias for {@link #scopeName}.
	 * @see #scopeName
	 */
	@AliasFor("scopeName")
	String value() default "";

	/**
	 * Specifies the name of the scope to use for the annotated component/bean.
	 * <p>Defaults to an empty string ({@code ""}) which implies
	 * {@link ConfigurableBeanFactory#SCOPE_SINGLETON SCOPE_SINGLETON}.
	 * @since 4.2
	 * @see ConfigurableBeanFactory#SCOPE_PROTOTYPE
	 * @see ConfigurableBeanFactory#SCOPE_SINGLETON
	 * @see org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST
	 * @see org.springframework.web.context.WebApplicationContext#SCOPE_SESSION
	 * @see #value
	 */
	@AliasFor("value")
	String scopeName() default "";

	/**
	 * Specifies whether a component should be configured as a scoped proxy
	 * and if so, whether the proxy should be interface-based or subclass-based.
	 * <p>Defaults to {@link ScopedProxyMode#DEFAULT}, which typically indicates
	 * that no scoped proxy should be created unless a different default
	 * has been configured at the component-scan instruction level.
	 * <p>Analogous to {@code <aop:scoped-proxy/>} support in Spring XML.
	 * @see ScopedProxyMode
	 */
	ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;

}

從源碼的註釋上,我們可以知道scopeName可以取下面這些值:前兩個用的比較多,我們就來看看前面兩個可以取的值

ConfigurableBeanFactory#SCOPE_PROTOTYPE
ConfigurableBeanFactory#SCOPE_SINGLETON
org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST
org.springframework.web.context.WebApplicationContext#SCOPE_SESSION

我們可以點到ConfigurableBeanFactory接口去看一下:

public interface ConfigurableBeanFactory extends HierarchicalBeanFactory, SingletonBeanRegistry {
    String SCOPE_SINGLETON = "singleton";
    String SCOPE_PROTOTYPE = "prototype";

    void setParentBeanFactory(BeanFactory var1) throws IllegalStateException;

    void setBeanClassLoader(ClassLoader var1);

    ClassLoader getBeanClassLoader();

    void setTempClassLoader(ClassLoader var1);

    ClassLoader getTempClassLoader();

    void setCacheBeanMetadata(boolean var1);

    boolean isCacheBeanMetadata();

    void setBeanExpressionResolver(BeanExpressionResolver var1);

    BeanExpressionResolver getBeanExpressionResolver();

    void setConversionService(ConversionService var1);

    ConversionService getConversionService();

    void addPropertyEditorRegistrar(PropertyEditorRegistrar var1);

    void registerCustomEditor(Class<?> var1, Class<? extends PropertyEditor> var2);

    void copyRegisteredEditorsTo(PropertyEditorRegistry var1);

    void setTypeConverter(TypeConverter var1);

    TypeConverter getTypeConverter();

    void addEmbeddedValueResolver(StringValueResolver var1);

    boolean hasEmbeddedValueResolver();

    String resolveEmbeddedValue(String var1);

    void addBeanPostProcessor(BeanPostProcessor var1);

    int getBeanPostProcessorCount();

    void registerScope(String var1, Scope var2);

    String[] getRegisteredScopeNames();

    Scope getRegisteredScope(String var1);

    AccessControlContext getAccessControlContext();

    void copyConfigurationFrom(ConfigurableBeanFactory var1);

    void registerAlias(String var1, String var2) throws BeanDefinitionStoreException;

    void resolveAliases(StringValueResolver var1);

    BeanDefinition getMergedBeanDefinition(String var1) throws NoSuchBeanDefinitionException;

    boolean isFactoryBean(String var1) throws NoSuchBeanDefinitionException;

    void setCurrentlyInCreation(String var1, boolean var2);

    boolean isCurrentlyInCreation(String var1);

    void registerDependentBean(String var1, String var2);

    String[] getDependentBeans(String var1);

    String[] getDependenciesForBean(String var1);

    void destroyBean(String var1, Object var2);

    void destroyScopedBean(String var1);

    void destroySingletons();
}

我們來指定一個多實例的:

@Configuration
public class MainConfig2 {
    //singleton:單實例的
    //prototype:多實例的
    //request:同一次請求創建一個實例
    //session:同一個session創建的一個實例
    @Scope("prototype")
    //默認是單實例的
    @Bean("person")
    public Person person() {
        return new Person();
    }

}

現在,我們再來測試一次:

    @Test
    public void test02() {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
        String[] definitionNames = applicationContext.getBeanDefinitionNames();
        for (String name : definitionNames) {
            System.out.println(name);
        }
        //默認是單實例的
        Person person1 = (Person) applicationContext.getBean("person");
        Person person2 = (Person) applicationContext.getBean("person");
        System.out.println(person1==person2);
    }

這個時候的測試結果如下:

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
mainConfig2
person
false

這個時候,bean的實例就多實例的,每調用一次getBean()方法就會創建一個實例;


我們來看看當bean的作用域爲單例的時候,它在IOC容器中是何時創建的:

@Configuration
public class MainConfig2 {
    
    @Scope
    @Bean("person")
    public Person person() {
        System.out.println("給IOC容器中添加Person...");
        return new Person();
    }

}

首先,我們先啓動IOC容器,但是不調用getBean方法來獲取Person實例:

    @Test
    public void test02() {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
    }

測試結果如下:

給IOC容器中添加Person…

這個時候,我們就可以發現,當作用域爲單例的時候,IOC容器在啓動的時候,就會將容器中所有作用域爲單例的bean的實例給創建出來;以後的每次獲取,就直接從IOC容器中來獲取,相當於是從map.get()的一個過程;


然而,當我們的bean的作用域改成多實例的時候,我們再看看結果:

@Configuration
public class MainConfig2 {

    @Scope("prototype")
    @Bean("person")
    public Person person() {
        System.out.println("給IOC容器中添加Person...");
        return new Person();
    }

}

當我們再運行的時候:

    @Test
    public void test02() {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
    }

我們可以發現,控制檯沒有任何的輸出結果;在IOC容器創建的時候,沒有去創建這個作用域爲多實例的bean;

這個時候,我們來調用getBean()方法來獲取一下:

    @Test
    public void test02() {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
        System.out.println("IOC容器創建完成...");
        Person person = (Person) applicationContext.getBean("person");
    }

這個時候,控制檯打印了:

IOC容器創建完成…
給IOC容器中添加Person…

同時, 如果我多次獲取:

    @Test
    public void test02() {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
        System.out.println("IOC容器創建完成...");
        Person person = (Person) applicationContext.getBean("person");
        Person person1 = (Person) applicationContext.getBean("person");
    }

測試結果如下:

IOC容器創建完成…
給IOC容器中添加Person…
給IOC容器中添加Person…

我們可以發現,我們用getBean方法獲取幾次,就創建幾次bean的實例;

也就是說當bean是作用域爲多例的時候,IOC容器啓動的時候,就不會去創建bean的實例的,而是當我們調用getBean()獲取的時候去創建bean的實例;而且每次調用的時候,都會創建bean的實例;

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