spring容器有两个基本的概念,分别是IoC和DI。
IoC:即控制反转,即创建对象的控制权进行了转移,将类的创建交给Spring容器类来管理,它就是一个专门用来创建对象的工厂。
DI:即依赖注入,将类里面的属性在创建对象的过程中给属性赋值。即容器动态的将某个依赖关系注入到组件之中。
关于这两个术语的论述可以参看编程大师Martin Fowler的文章:IoC和DI模式
IoC和DI的关系:同一个概念的不同角度的描述,DI这个概念由Martin Flowler提出,这个术语明确描述了被注入对象依赖IoC容器配置依赖对象。
IOC是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合,更优良的程序。传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合。
IOC很好的体现了面向对象法则之一:好莱坞法则:别找我们,我们找你,即由IOC容器帮对象找相应的依赖对象并注入,而不是由对象主动去找。
在讲使用@Configuration和@Bean注解加载bean之前,我们先看看如何使用xml加载bean。
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Person implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private Integer age;
}
这个bean会被加载到spring容器中,被容器管理
<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 id="person" class="win.elegentjs.spring.ioc.beans.Person">
<property name="name" value="liu"/>
<property name="age" value="20"/>
bean>
beans>
/**
* 基于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);
}
}
使用@Configuration搭配@Bean可实现编码方式注册bean,用法更简洁,此特性从spring3开始支持,称为spring java config。一般约定配置类俗成建在config目录中,并以xxxConfig结尾。
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);
}
}
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。
package win.elegentjs.spring.ioc.compos;
import org.springframework.stereotype.Component;
@Component
public class MyComponent {
@Override
public String toString() {
return "this is a component.";
}
}
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三者功能相同,只是用于业务上区分。
/**
* 基于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的源码:
/*
* 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 } element.
*
* 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.
*
*
Note that the {@code } 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.
*
* 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}.
* Allows for more concise annotation declarations if no other attributes
* are needed — 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.
* {@link #value} is an alias for (and mutually exclusive with) this
* attribute.
*
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.
* 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.
* 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.
* The default is defer to the default behavior of the component scanner used to
* execute the actual scan.
*
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.
* 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.
* 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.
*
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.
* 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.
* 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.
* The following table explains how the classes will be interpreted
* based on the configured value of the {@link #type} attribute.
*
* {@code FilterType} Class Interpreted As
* {@link FilterType#ANNOTATION ANNOTATION}
* the annotation itself
* {@link FilterType#ASSIGNABLE_TYPE ASSIGNABLE_TYPE}
* the type that detected components should be assignable to
* {@link FilterType#CUSTOM CUSTOM}
* an implementation of {@link TypeFilter}
*
* When multiple classes are specified, OR logic is applied
* — for example, "include types annotated with {@code @Foo} OR {@code @Bar}".
*
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}:
*
* - {@link org.springframework.context.EnvironmentAware EnvironmentAware}
* - {@link org.springframework.beans.factory.BeanFactoryAware BeanFactoryAware}
*
- {@link org.springframework.beans.factory.BeanClassLoaderAware BeanClassLoaderAware}
*
- {@link org.springframework.context.ResourceLoaderAware ResourceLoaderAware}
*
* 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}.
* 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以外还有以下常用的属性:
@ComponentScan(value = "win.elegentjs", includeFilters = {
@Filter(type = FilterType.ANNOTATION, classes = {Controller.class})
}, useDefaultFilters = false)
注意需要关闭默认的Filter。
@ComponentScan(value = "win.elegentjs", excludeFilters = {
@Filter(type = FilterType.ANNOTATION, classes = {Controller.class, Service.class, Repository.class})
})
是一个枚举值,包含:
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
}
枚举值的含义从注释里也能看得出来,以下再做个解释:
@ComponentScan(value = "win.elegentjs", includeFilters = {
@Filter(type = FilterType.ANNOTATION, classes = {Controller.class})
}, useDefaultFilters = false)
@ComponentScan(value = "win.elegentjs", includeFilters = {
@Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {PersonService.class})
}, useDefaultFilters = false)
即:当PersonService是一个Java类时,PersonService及其子类都会被加载到spring容器;当PersonService是一个接口时,其子接口或实现类都会被加载到spring容器中。
@ComponentScan(value = "win.elegentjs", includeFilters = {
@Filter(type = FilterType.ASPECTJ, classes = {AspectJTypeFilter.class})
}, useDefaultFilters = false)
@ComponentScan(value = "win.elegentjs", includeFilters = {
@Filter(type = FilterType.REGEX, classes = {RegexPatternTypeFilter.class})
}, useDefaultFilters = false)
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自定义规则见示例源码