2. spring-容器: 注解:@Configuration; @Bean; @ComponentScan

本文深入解析Spring框架中的IoC(控制反转)与DI(依赖注入)核心概念,介绍这两种机制如何帮助开发者构建松耦合的应用程序。同时,文章详细讲解了使用XML配置和注解方式在Spring容器中注册Bean的过程。
IoC 和 DI

spring容器有两个基本的概念,分别是IoC和DI。

IoC:即控制反转,即创建对象的控制权进行了转移,将类的创建交给Spring容器类来管理,它就是一个专门用来创建对象的工厂。
DI:即依赖注入,将类里面的属性在创建对象的过程中给属性赋值。即容器动态的将某个依赖关系注入到组件之中。

关于这两个术语的论述可以参看编程大师Martin Fowler的文章:IoC和DI模式

IoC和DI的关系:同一个概念的不同角度的描述,DI这个概念由Martin Flowler提出,这个术语明确描述了被注入对象依赖IoC容器配置依赖对象。

IoC和DI能做什么

IOC是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合,更优良的程序。传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合。
IOC很好的体现了面向对象法则之一:好莱坞法则:别找我们,我们找你,即由IOC容器帮对象找相应的依赖对象并注入,而不是由对象主动去找。

使用xml方式注册Bean

在讲使用@Configuration和@Bean注解加载bean之前,我们先看看如何使用xml加载bean。

  1. 步骤一:先建立一个通用Bean
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Person implements Serializable {

    private static final long serialVersionUID = 1L;

    private String name;

    private Integer age;

}

这个bean会被加载到spring容器中,被容器管理

  1. 步骤二:新建applicationContext.xml并配置bean
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context
                           http://www.springframework.org/context/spring-context.xsd ">
	
	<!-- 使用bean结点注册一个bean, id='person' 表示bean name -->
    <bean id="person" class="win.elegentjs.spring.ioc.beans.Person">
        <property name="name" value="liu"/>
        <property name="age" value="20"/>
    </bean>
</beans>
  1. 步骤三:写测试类加载xml,初始化容器,并从容器中取出bean打印出来。
/**
 * 基于xml方式加载bean
 */
@Slf4j
public class XmlBeanSample {

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

        Person p = context.getBean("person", Person.class);

        log.info("person: {}", p);

    }
}


打印结果:
在这里插入图片描述

使用注解方式注册bean

使用@Configuration搭配@Bean可实现编码方式注册bean,用法更简洁,此特性从spring3开始支持,称为spring java config。一般约定配置类俗成建在config目录中,并以xxxConfig结尾。

  1. 新建配置类PersonConfig
package win.elegentjs.spring.ioc.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import win.elegentjs.spring.ioc.beans.Person;

@Configuration
public class PersonConfig {

    @Bean
    public Person myPerson() {
        return new Person("张三", 30);
    }
}

注:bean name由默认由方法决定,在上面的示例bean name就是myPerson,如果想换一种是改方法名,另一种是通过@Bean的value或name属性指定,如:

package win.elegentjs.spring.ioc.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import win.elegentjs.spring.ioc.beans.Person;

@Configuration
public class PersonConfig {

    @Bean(name = "customerPerson")
    public Person myPerson() {
        return new Person("张三", 30);
    }
}
  1. 写测试类通过注解方式加载bean,并从容器中取出来打印
package win.elegentjs.spring.ioc;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import win.elegentjs.spring.ioc.beans.Person;
import win.elegentjs.spring.ioc.config.PersonConfig;

/**
 * 基于注解方式加载bean
 */
@Slf4j
public class AnnotationBeanSample {

    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(PersonConfig.class);

        Person p = context.getBean(Person.class);

        log.info("person: {}", p);

    }
}

运行结果:
在这里插入图片描述

使用@ComponentScan注解扫描待注册的bean

使用@ComponentScan注解配合一些实用属性可以灵活的配置待加载的bean。

  1. 新建一个类, 使用@Component进行注解。
package win.elegentjs.spring.ioc.compos;

import org.springframework.stereotype.Component;

@Component
public class MyComponent {

    @Override
    public String toString() {
        return "this is a component.";
    }
}
  1. 在PersonConfig类上加上注解@ComponentScan, 并配置basePackages
package win.elegentjs.spring.ioc.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import win.elegentjs.spring.ioc.beans.Person;

@Configuration
@ComponentScan(basePackages = "win.elegentjs")
public class PersonConfig {

    @Bean
    public Person myPerson() {
        return new Person("张三", 30);
    }
}

加上这段配置后表示spring容器启动后自动扫描指定包路径下的所有类,当发现类上有以下注解:@Component, @Controller, @Service, @Repository, @Configuration时就自动把它们当作要加载的bean,从而加载到spring容器中。

注:@Component, @Service, @Repository三者功能相同,只是用于业务上区分。

  1. 使用测试类测试当前容器中有哪些bean
/**
 * 基于Annotation配置componentScan方式加载bean
 */
@Slf4j
public class AnnotationComponentScanSample {

    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(PersonConfig.class);

        String[] beanDefinitions = context.getBeanDefinitionNames();
        log.info("beanDefinitions: {}", ArraysUtil.toString(beanDefinitions));

    }
}

运行结果:
在这里插入图片描述
可以看出除了使用@Component注解的bean myComponent被加载了,其他@Configuration和该类下@Bean的也被加载了,并且还包含一些spring容器内部的bean。

@ComponentScan一些常见的属性

一起来看下ComponentScan的源码:

/*
 * Copyright 2002-2016 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.context.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.core.annotation.AliasFor;
import org.springframework.core.type.filter.TypeFilter;

/**
 * Configures component scanning directives for use with @{@link Configuration} classes.
 * Provides support parallel with Spring XML's {@code <context:component-scan>} element.
 *
 * <p>Either {@link #basePackageClasses} or {@link #basePackages} (or its alias
 * {@link #value}) may be specified to define specific packages to scan. If specific
 * packages are not defined, scanning will occur from the package of the
 * class that declares this annotation.
 *
 * <p>Note that the {@code <context:component-scan>} element has an
 * {@code annotation-config} attribute; however, this annotation does not. This is because
 * in almost all cases when using {@code @ComponentScan}, default annotation config
 * processing (e.g. processing {@code @Autowired} and friends) is assumed. Furthermore,
 * when using {@link AnnotationConfigApplicationContext}, annotation config processors are
 * always registered, meaning that any attempt to disable them at the
 * {@code @ComponentScan} level would be ignored.
 *
 * <p>See {@link Configuration @Configuration}'s Javadoc for usage examples.
 *
 * @author Chris Beams
 * @author Juergen Hoeller
 * @author Sam Brannen
 * @since 3.1
 * @see Configuration
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {

	/**
	 * Alias for {@link #basePackages}.
	 * <p>Allows for more concise annotation declarations if no other attributes
	 * are needed &mdash; for example, {@code @ComponentScan("org.my.pkg")}
	 * instead of {@code @ComponentScan(basePackages = "org.my.pkg")}.
	 */
	@AliasFor("basePackages")
	String[] value() default {};

	/**
	 * Base packages to scan for annotated components.
	 * <p>{@link #value} is an alias for (and mutually exclusive with) this
	 * attribute.
	 * <p>Use {@link #basePackageClasses} for a type-safe alternative to
	 * String-based package names.
	 */
	@AliasFor("value")
	String[] basePackages() default {};

	/**
	 * Type-safe alternative to {@link #basePackages} for specifying the packages
	 * to scan for annotated components. The package of each class specified will be scanned.
	 * <p>Consider creating a special no-op marker class or interface in each package
	 * that serves no purpose other than being referenced by this attribute.
	 */
	Class<?>[] basePackageClasses() default {};

	/**
	 * The {@link BeanNameGenerator} class to be used for naming detected components
	 * within the Spring container.
	 * <p>The default value of the {@link BeanNameGenerator} interface itself indicates
	 * that the scanner used to process this {@code @ComponentScan} annotation should
	 * use its inherited bean name generator, e.g. the default
	 * {@link AnnotationBeanNameGenerator} or any custom instance supplied to the
	 * application context at bootstrap time.
	 * @see AnnotationConfigApplicationContext#setBeanNameGenerator(BeanNameGenerator)
	 */
	Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

	/**
	 * The {@link ScopeMetadataResolver} to be used for resolving the scope of detected components.
	 */
	Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;

	/**
	 * Indicates whether proxies should be generated for detected components, which may be
	 * necessary when using scopes in a proxy-style fashion.
	 * <p>The default is defer to the default behavior of the component scanner used to
	 * execute the actual scan.
	 * <p>Note that setting this attribute overrides any value set for {@link #scopeResolver}.
	 * @see ClassPathBeanDefinitionScanner#setScopedProxyMode(ScopedProxyMode)
	 */
	ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;

	/**
	 * Controls the class files eligible for component detection.
	 * <p>Consider use of {@link #includeFilters} and {@link #excludeFilters}
	 * for a more flexible approach.
	 */
	String resourcePattern() default ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN;

	/**
	 * Indicates whether automatic detection of classes annotated with {@code @Component}
	 * {@code @Repository}, {@code @Service}, or {@code @Controller} should be enabled.
	 */
	boolean useDefaultFilters() default true;

	/**
	 * Specifies which types are eligible for component scanning.
	 * <p>Further narrows the set of candidate components from everything in {@link #basePackages}
	 * to everything in the base packages that matches the given filter or filters.
	 * <p>Note that these filters will be applied in addition to the default filters, if specified.
	 * Any type under the specified base packages which matches a given filter will be included,
	 * even if it does not match the default filters (i.e. is not annotated with {@code @Component}).
	 * @see #resourcePattern()
	 * @see #useDefaultFilters()
	 */
	Filter[] includeFilters() default {};

	/**
	 * Specifies which types are not eligible for component scanning.
	 * @see #resourcePattern
	 */
	Filter[] excludeFilters() default {};

	/**
	 * Specify whether scanned beans should be registered for lazy initialization.
	 * <p>Default is {@code false}; switch this to {@code true} when desired.
	 * @since 4.1
	 */
	boolean lazyInit() default false;


	/**
	 * Declares the type filter to be used as an {@linkplain ComponentScan#includeFilters
	 * include filter} or {@linkplain ComponentScan#excludeFilters exclude 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 {};

	}

}

除了basePackages以外还有以下常用的属性:

  1. includeFilters
    表示Spring扫描的时候,只包含哪些注解,如:
@ComponentScan(value = "win.elegentjs", includeFilters = {
        @Filter(type = FilterType.ANNOTATION, classes = {Controller.class})
}, useDefaultFilters = false)

注意需要关闭默认的Filter。

  1. excludeFilters
    表示不包含哪些注解, 如:
@ComponentScan(value = "win.elegentjs", excludeFilters = {
        @Filter(type = FilterType.ANNOTATION, classes = {Controller.class, Service.class, Repository.class})
})

关注@Filter的type属性

是一个枚举值,包含:

package org.springframework.context.annotation;

/**
 * Enumeration of the type filters that may be used in conjunction with
 * {@link ComponentScan @ComponentScan}.
 *
 * @author Mark Fisher
 * @author Juergen Hoeller
 * @author Chris Beams
 * @since 2.5
 * @see ComponentScan
 * @see ComponentScan#includeFilters()
 * @see ComponentScan#excludeFilters()
 * @see org.springframework.core.type.filter.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

}

枚举值的含义从注释里也能看得出来,以下再做个解释:

  • ANNOTATION:通过注解过滤,最常用
@ComponentScan(value = "win.elegentjs", includeFilters = {
    @Filter(type = FilterType.ANNOTATION, classes = {Controller.class})
}, useDefaultFilters = false)

  • ASSIGNABLE_TYPE:通过类过滤,符合该类的组件(本类及子类)才会加载到容器
@ComponentScan(value = "win.elegentjs", includeFilters = {
    @Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {PersonService.class})
}, useDefaultFilters = false)

即:当PersonService是一个Java类时,PersonService及其子类都会被加载到spring容器;当PersonService是一个接口时,其子接口或实现类都会被加载到spring容器中。

  • ASPECTJ:通过AspectJ表达式过滤
@ComponentScan(value = "win.elegentjs", includeFilters = {
    @Filter(type = FilterType.ASPECTJ, classes = {AspectJTypeFilter.class})
}, useDefaultFilters = false)

  • REGEX:通过正则表达式过滤
@ComponentScan(value = "win.elegentjs", includeFilters = {
    @Filter(type = FilterType.REGEX, classes = {RegexPatternTypeFilter.class})
}, useDefaultFilters = false)

  • CUSTOM:通过自定义规则进行过滤
    通过自定义规则进行过滤时,自定义规则的类必须实现org.springframework.core.type.filter.TypeFilter接口。如下:
public class MyTypeFilter implements TypeFilter {
    @Override
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
        return false;
    }
}

match方法返回布尔值, 为true时表示符合规则,会加载到spring容器;当返回false,表示不符合规则,不会包含在spring容器中。然后ComponentScan:

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

CUSTOM自定义规则见示例源码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值