文章目录
- 1、使用@Configuration和@Bean给容器中注册组件
- 2、使用@ComponentScan自动扫描组件并指定扫描规则
- 3、自定义TypeFilter指定@ComponentScan注解的过滤规则
- 4、使用@Scope注解设置组件的作用域
- 5、@Lazy-bean如何实现懒加载?
- 6、如何按照条件向Spring容器中注册bean?
- 7、使用@Import注解给容器中快速导入一个组件
- 8、如何使用FactoryBean向Spring容器中注册bean?
- 9、如何使用@Bean注解指定初始化和销毁的方法?
- 10、使用InitializingBean和DisposableBean来管理bean的生命周期
- 11、@PostConstruct注解和@PreDestroy注解
- 12、BeanPostProcessor后置处理器
- 13、BeanPostProcessor的执行流程
- 14、BeanPostProcessor在Spring底层是如何使用的?
- 15、使用@Value注解为bean的属性赋值
- 16、使用@Autowired、@Qualifier、@Primary这三大注解自动装配组件
- 17、@Resource注解和@Inject注解
- 18、实现方法、构造器位置的自动装配
- 19、自定义组件中如何注入Spring底层的组件?
- 20、使用@Profile注解实现开发、测试和生产环境的配置和切换
1、使用@Configuration和@Bean给容器中注册组件
首先创建一个Person类:
public class Person {
private String name;
private Integer age;
......
1.1、xml配置文件方法
创建一个beans.xml
,在配置文件中指定<bean>
标签,其中属性包括id(唯一标识)
和class(全类名)
:
<bean id="person" class="bean.Person">
<property name="age" value="18"></property>
<property name="name" value="zhangsan"/>
</bean>
测试时,使用ClassPathXmlApplicationContext()
方法传入xml配置文件的路径来获取一个ApplicationContext对象:
@Test
public void test1(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
Object person = applicationContext.getBean("person");
System.out.println(person);
// Person{name='zhangsan', age=18}
}
1.2、注解配置
创建一个注解类MainConfig,相当于一个配置文件,采用@Configuration
注解。
在类中创建一个方法,采用@Bean
注解,类型为返回值类型,id默认是用方法名作为id,也可以给@Bean
注解传入一个参数,作为id:
//配置类==配置文件
@Configuration //告诉spring,这是一个配置类
public class MainConfig {
//给容器中注册一个Bean;类型为返回值类型,id默认是用方法名作为id
@Bean("person") //可以传入参数,作为id
public Person person01(){
return new Person("lisi", 20);
}
}
获取Bean时,使用AnnotationConfigApplicationContext()
方法传入注解类的class:
@Test
public void test2(){
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
Person person = applicationContext.getBean(Person.class);
System.out.println(person);
// Person{name='lisi', age=20}
String[] beanNamesForType = applicationContext.getBeanNamesForType(Person.class);
for (String string :
beanNamesForType) {
System.out.println(string);
// person
}
}
2、使用@ComponentScan自动扫描组件并指定扫描规则
创建BookService、BookController、BookDao类,并分别使用@Service
、@Controller
、@Repository
注解:
@Service
public class BookService {
}
@Controller
public class BookController {
}
@Repository
public class BookDao {
}
2.1、xml配置文件方式
<!--包扫描,只要标注了@Controller、@Service、@Repository、@Component-->
<!--<context:component-scan base-package={"bean", "config", "controller", "dao", "service"}></context:component-scan>-->
2.2、注解方式
在注解类中添加@ComponentScan
注解,并提供value属性的值:
//配置类==配置文件
@Configuration //告诉spring,这是一个配置类
@ComponentScan(value = {"bean", "config", "controller", "dao", "service"})
public class MainConfig {
//给容器中注册一个Bean;类型为返回值类型,id默认是用方法名作为id
@Bean("person") //可以传入参数,作为id
public Person person01(){
return new Person("lisi", 20);
}
}
输出一下所有定义的Bean的名字:
@Test
public void test3(){
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
String[] definitionNames = applicationContext.getBeanDefinitionNames();
for (String name :
definitionNames) {
System.out.println(name);
}
}
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
mainConfig
bookController
bookDao
bookService
person
2.2.1、扫描规则:excludeFilters
看一下@ComponentScan
注解的源码:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
//可重复的
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
@AliasFor("basePackages")
String[] value() default {};
@AliasFor("value")
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;
ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;
String resourcePattern() default "**/*.class";
//默认扫描规则
boolean useDefaultFilters() default true;
//包含哪些包
ComponentScan.Filter[] includeFilters() default {};
//排除哪些包
ComponentScan.Filter[] excludeFilters() default {};
......
可以看到includeFilters()
和excludeFilters()
返回值都是ComponentScan.Filter[]
,我们查看ComponentScan.Filter[]
:
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface Filter {
FilterType type() default FilterType.ANNOTATION;
@AliasFor("classes")
Class<?>[] value() default {};
@AliasFor("value")
Class<?>[] classes() default {};
String[] pattern() default {};
}
可以看到,需要指定type属性和classes(或value,都是一个数组,可以指定多个):
//配置类==配置文件
@Configuration //告诉spring,这是一个配置类
@ComponentScan(value = {"bean", "config", "controller", "dao", "service"},
excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class, Service.class})})
//@ComponentScan value:指定要扫描的包
//@ComponentScan excludeFilters = ComponentScan.Filter[]:排除哪些包
public class MainConfig {
//给容器中注册一个Bean;类型为返回值类型,id默认是用方法名作为id
@Bean("person") //可以传入参数,作为id
public Person person01(){
return new Person("lisi", 20);
}
}
再次测试:
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
mainConfig
bookDao
person
2.2.2、扫描规则:includeFilters
在xml配置文件配置中,使用includeFilters需要先禁用默认扫描规则(因为默认是全部扫描的):
<!--包扫描,只要标注了@Controller、@Service、@Repository、@Component-->
<context:component-scan use-default-filters="false" base-package={"bean", "config", "controller", "dao", "service"} ></context:component-scan>
查看上面的@ComponentScan
注解的源码,可以发现其中也有默认的扫描规则,useDefaultFilters默认为true,因为需要先设定其为false,然后按照相同的方法配置includeFilters:
//配置类==配置文件
@Configuration //告诉spring,这是一个配置类
@ComponentScan(
value = {"bean", "config", "controller", "dao", "service"},
includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class})},
useDefaultFilters = false
//excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class})}
)
//@ComponentScan value:指定要扫描的包
//@ComponentScan excludeFilters = ComponentScan.Filter[]:排除哪些包
//@ComponentScan includeFilters = ComponentScan.Filter[]:只要哪些包
public class MainConfig {
//给容器中注册一个Bean;类型为返回值类型,id默认是用方法名作为id
@Bean("person") //可以传入参数,作为id
public Person person01(){
return new Person("lisi", 20);
}
}
再次测试:
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
mainConfig
bookController
person
2.2.3、配置多个ComponentScan
查看上面的@ComponentScan
注解的源码,可以发现有@Repeatable
注解,说明可以设定多个@ComponentScan
:
//配置类==配置文件
@Configuration //告诉spring,这是一个配置类
@ComponentScan(
value = {"bean", "config", "controller", "dao", "service"},
includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class})},
useDefaultFilters = false
//excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class})}
)
//@ComponentScan value:指定要扫描的包
//@ComponentScan excludeFilters = ComponentScan.Filter[]:排除哪些包
//@ComponentScan includeFilters = ComponentScan.Filter[]:只要哪些包
@ComponentScans(
value = {
@ComponentScan(
value = {"bean", "config", "controller", "dao", "service"},
includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class})},
useDefaultFilters = false
//excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class})}
)
}
)
public class MainConfig {
//给容器中注册一个Bean;类型为返回值类型,id默认是用方法名作为id
@Bean("person") //可以传入参数,作为id
public Person person01(){
return new Person("lisi", 20);
}
}
3、自定义TypeFilter指定@ComponentScan注解的过滤规则
先来查看一下@ComponentScan.Filter
中的FilterType:
public enum FilterType {
ANNOTATION,
ASSIGNABLE_TYPE,
ASPECTJ,
REGEX,
CUSTOM;
private FilterType() {
}
}
是一个枚举类型。其中:
- ANNOTATION:按照注解方式
- ASSIGNABLE_TYPE:按照类型
- ASPECTJ:按照ASPECTJ,不常用
- REGEX:按照正则表达式
- CUSTOM:按照自定义类型
创建一个类MyTypeFilter继承TypeFilter:
public class MyTypeFilter implements TypeFilter {
/**
*
* @param metadataReader 读取到的当前正在扫描的类的信息
* @param metadataReaderFactory 可以获取到其他任何类信息的
* @return
* @throws IOException
*/
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
//获取当前类注解的信息
AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
//获取当前正在扫描的类的类信息
ClassMetadata classMetadata = metadataReader.getClassMetadata();
//获取当前类资源(类的路径)
Resource resource = metadataReader.getResource();
String className = classMetadata.getClassName();
System.out.println("--->" + className);
//如果类名称包含"er"就扫描
if (className.contains("er")){
return true;
}
return false;
}
}
//配置类==配置文件
@Configuration //告诉spring,这是一个配置类
@ComponentScan(
value = {"bean", "config", "controller", "dao", "service"},
includeFilters = {
/* @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class}),
@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {BookService.class}),*/
@ComponentScan.Filter(type = FilterType.CUSTOM, classes = {MyTypeFilter.class})
},
useDefaultFilters = false
//excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class})}
)
//@ComponentScan value:指定要扫描的包
//@ComponentScan excludeFilters = ComponentScan.Filter[]:排除哪些包
//@ComponentScan includeFilters = ComponentScan.Filter[]:只要哪些包
//FilterType.ANNOTATION:按照注解
//FilterType.ASSIGNABLE_TYPE:按照给定类型
//FilterType.ASPECTJ:按照ASPECTJ表达式
//FilterType.REGEX:按照正则表达式
//FilterType.CUSTOM:按照自定义规则
@ComponentScans(
value = {
@ComponentScan(
value = {"bean", "config", "controller", "dao", "service"},
includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class})},
useDefaultFilters = false
//excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class})}
)
}
)
public class MainConfig {
//给容器中注册一个Bean;类型为返回值类型,id默认是用方法名作为id
@Bean("person") //可以传入参数,作为id
public Person person01(){
return new Person("lisi", 20);
}
}
--->bean.Person
--->config.MyTypeFilter
--->controller.BookController
--->dao.BookDao
--->service.BookService
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
mainConfig
person
myTypeFilter
bookController
bookService
4、使用@Scope注解设置组件的作用域
创建一个配置类MainConfig2:
@Configuration
public class MainConfig2 {
//默认是单实例
@Bean("person")
public Person person(){
return new Person("张三", 5);
}
}
默认情况下是**单实例的,即不管多少次getBean()**,获取到的都是那一个new Person("张三", 5)
:
@Test
public void test4(){
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
String[] definitionNames = applicationContext.getBeanDefinitionNames();
//默认是单实例
Object bean = applicationContext.getBean("person");
Object bean2 = applicationContext.getBean("person");
System.out.println(bean == bean2); //true
}
可以使用@Scope
注解为其配置实现单实例还是多实例,查看@Scope
源码:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Scope {
@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 "";
ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;
}
发现有四个取值:
- ConfigurableBeanFactory.SCOPE_PROTOTYPE, prototype:多实例
- ConfigurableBeanFactory.SCOPE_SINGLETON, singleton:单实例(默认值)
- org.springframework.web.context.WebApplicationContext.SCOPE_REQUEST, request:同一个请求创建一个实例
- org.springframework.web.context.WebApplicationContext.SCOPE_SESSION, session:同一个session创建一个实例
@Configuration
public class MainConfig2 {
//默认是单实例
/**
* ConfigurableBeanFactory.SCOPE_PROTOTYPE, prototype:多实例
* ConfigurableBeanFactory.SCOPE_SINGLETON, singleton:单实例(默认值)
* org.springframework.web.context.WebApplicationContext.SCOPE_REQUEST, request:同一个请求创建一个实例
* org.springframework.web.context.WebApplicationContext.SCOPE_SESSION, session:同一个session创建一个实例
* @return
*/
@Scope("prototype")
@Bean("person")
public Person person(){
return new Person("张三", 5);
}
}
@Test
public void test4(){
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
String[] definitionNames = applicationContext.getBeanDefinitionNames();
//默认是单实例
Object bean = applicationContext.getBean("person");
Object bean2 = applicationContext.getBean("person");
System.out.println(bean);
System.out.println(bean2);
System.out.println(bean == bean2); //false
}
Person{name='张三', age=5}
Person{name='张三', age=5}
false
在xml配置文件中可以使用<bean>
标签中的scope属性实现:
<bean id="person" class="bean.Person" scope="prototype">
<property name="age" value="18"></property>
<property name="name" value="zhangsan"/>
</bean>
4.1、单实例和多实例情况下,对象是何时创建的?
先来看单实例情况,在return new Person("张三", 5);
调用创建对象的方法之前添加一个System.out.println("给容器中添加Pserson...");
语句:
@Configuration
public class MainConfig2 {
//默认是单实例
/**
* ConfigurableBeanFactory.SCOPE_PROTOTYPE, prototype:多实例
* ConfigurableBeanFactory.SCOPE_SINGLETON, singleton:单实例(默认值)
* org.springframework.web.context.WebApplicationContext.SCOPE_REQUEST, request:同一个请求创建一个实例
* org.springframework.web.context.WebApplicationContext.SCOPE_SESSION, session:同一个session创建一个实例
*
* prototype:多实例,IOC容器启动并不会调用方法创建对象放到IOC容器中
* 以后每次获取时才会调用方法创建对象
* singleton:单实例(默认值),IOC容器启动时会调用方法创建对象,并放到IOC容器中
* 以后每次获取就从容器中拿,因此是单实例
*
* @return
*/
@Scope //调整作用域
@Bean("person")
public Person person(){
System.out.println("给容器中添加Pserson...");
return new Person("张三", 5);
}
}
在测试代码中,先来创建IOC容器,并没有从容器中拿出对象:
@Test
public void test4(){
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
//String[] definitionNames = applicationContext.getBeanDefinitionNames();
//
//默认是单实例
System.out.println("IOC容器创建完成");
//Object bean = applicationContext.getBean("person");
//Object bean2 = applicationContext.getBean("person");
//System.out.println(bean);
//System.out.println(bean2);
//System.out.println(bean == bean2);
}
给容器中添加Pserson...
IOC容器创建完成
打印出给容器中添加Pserson...
。因此,可以推断:单实例(默认值),IOC容器启动时会调用方法创建对象,并放到IOC容器中。以后每次获取就从容器中拿,因此是单实例。
再看多实例情况:
@Configuration
public class MainConfig2 {
//默认是单实例
/**
* ConfigurableBeanFactory.SCOPE_PROTOTYPE, prototype:多实例
* ConfigurableBeanFactory.SCOPE_SINGLETON, singleton:单实例(默认值)
* org.springframework.web.context.WebApplicationContext.SCOPE_REQUEST, request:同一个请求创建一个实例
* org.springframework.web.context.WebApplicationContext.SCOPE_SESSION, session:同一个session创建一个实例
*
* prototype:多实例,IOC容器启动并不会调用方法创建对象放到IOC容器中
* 以后每次获取时才会调用方法创建对象
* singleton:单实例(默认值),IOC容器启动时会调用方法创建对象,并放到IOC容器中
* 以后每次获取就从容器中拿,因此是单实例
*
* @return
*/
@Scope("prototype") //调整作用域
@Bean("person")
public Person person(){
System.out.println("给容器中添加Pserson...");
return new Person("张三", 5);
}
}
依然只创建IOC容器:
@Test
public void test4(){
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
//String[] definitionNames = applicationContext.getBeanDefinitionNames();
//
//默认是单实例
//System.out.println("IOC容器创建完成");
//Object bean = applicationContext.getBean("person");
//Object bean2 = applicationContext.getBean("person");
//System.out.println(bean);
//System.out.println(bean2);
//System.out.println(bean == bean2);
}
并没有任何输出。因此可以推断:多实例,创建IOC 容器时,并不会创建对象。
开启获取对象,在获取对象前添加System.out.println("IOC容器创建完成");
:
@Test
public void test4(){
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
//String[] definitionNames = applicationContext.getBeanDefinitionNames();
//
//默认是单实例
System.out.println("IOC容器创建完成");
Object bean = applicationContext.getBean("person");
Object bean2 = applicationContext.getBean("person");
//System.out.println(bean);
//System.out.println(bean2);
//System.out.println(bean == bean2);
}
IOC容器创建完成
给容器中添加Pserson...
给容器中添加Pserson...
获取了两次对象,输出了两次给容器中添加Pserson...
。因此,可以推断:多实例,IOC容器启动并不会调用方法创建对象放到IOC容器中,以后每次获取时才会调用方法创建对象。
5、@Lazy-bean如何实现懒加载?
懒加载是针对单实例bean:
- 单实例bean默认在容器启动时创建对象;
- 懒加载:容器启动时不创建对象,第一次使用(获取)bean时创建对象,并初始化。
@Scope//("prototype") //调整作用域
@Lazy //懒加载
@Bean("person")
public Person person(){
System.out.println("给容器中添加Pserson...");
return new Person("张三", 5);
}
测试时,先不获取对象:
@Test
public void test4(){
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
//String[] definitionNames = applicationContext.getBeanDefinitionNames();
//
//默认是单实例
System.out.println("IOC容器创建完成");
//Object bean = applicationContext.getBean("person");
//Object bean2 = applicationContext.getBean("person");
//System.out.println(bean);
//System.out.println(bean2);
//System.out.println(bean == bean2);
}
IOC容器创建完成
只打印出IOC容器创建完成
。并没创建对象。
获取对象,并判断对象是否相等:
@Test
public void test4(){
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
//String[] definitionNames = applicationContext.getBeanDefinitionNames();
//
//默认是单实例
System.out.println("IOC容器创建完成");
Object bean = applicationContext.getBean("person");
Object bean2 = applicationContext.getBean("person");
System.out.println(bean);
System.out.println(bean2);
System.out.println(bean == bean2);
}
IOC容器创建完成
给容器中添加Pserson...
Person{name='张三', age=5}
Person{name='张三', age=5}
true
可以看到,虽然获取了两次对象,但只打印了一次给容器中添加Pserson...
,说明只调用了一次创建对象的方法,因此,两个对象相等。
6、如何按照条件向Spring容器中注册bean?
@Conditional()
:按照一定的条件进行判断,满足条件给容器中注册bean
先查看@Conditional()
源码:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
/**
* All {@link Condition} classes that must {@linkplain Condition#matches match}
* in order for the component to be registered.
*/
Class<? extends Condition>[] value();
}
可以看到@Target({ElementType.TYPE, ElementType.METHOD})
,表明:@Conditional()
可以用在类上也可以用在方法上。
Class<? extends Condition>[] value();
也表明:@Conditional()
的value值需要一个继承extends Condition
的类(.class)的列表。
查看Condition接口:
@FunctionalInterface
public interface Condition {
/**
* Determine if the condition matches.
* @param context the condition context
* @param metadata the metadata of the {@link org.springframework.core.type.AnnotationMetadata class}
* or {@link org.springframework.core.type.MethodMetadata method} being checked
* @return {@code true} if the condition matches and the component can be registered,
* or {@code false} to veto the annotated component's registration
*/
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
可知,需要重写matches()
方法。
我们的需求是,注册两个Person类,
如果系统是Windows,给容器中注册(“bill”);
如果系统是Linux,给容器中注册(“linux”)
创建两个类WindowsCondition和LinuxCondition,实现Condition接口,并重写matches()
方法:
//判断操作系统是否是Windows
public class WindowsCondition implements Condition {
/**
*
* @param context:判断条件能使用的上下文(环境)
* @param metadata:注释信息
* @return
*/
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
//是否Windows系统
//1、能获取到IOC使用的beanFactory
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
//2、获取类加载器
ClassLoader classLoader = context.getClassLoader();
//3、获取当前环境信息
Environment environment = context.getEnvironment();
//4、获取到bean定义的注册类
BeanDefinitionRegistry registry = context.getRegistry();
String property = environment.getProperty("os.name");
if (property.contains("Windows")){
return true;
}
return false;
}
}
//判断操作系统是否是Linux
public class LinuxCondition implements Condition {
/**
*
* @param context:判断条件能使用的上下文(环境)
* @param metadata:注释信息
* @return
*/
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
//是否Linux系统
//1、能获取到IOC使用的beanFactory
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
//2、获取类加载器
ClassLoader classLoader = context.getClassLoader();
//3、获取当前环境信息
Environment environment = context.getEnvironment();
//4、获取到bean定义的注册类
BeanDefinitionRegistry registry = context.getRegistry();
String property = environment.getProperty("os.name");
if (property.contains("Linux")){
return true;
}
//判断容器中的bean注册情况,也可以给容器中注册bean
boolean definition = registry.containsBeanDefinition("person");
return false;
}
}
修改配置类:
/**
* @Conditional(Class<? extends Condition>[]):按照一定的条件进行判断,满足条件给容器中注册bean
* 如果系统是Windows,给容器中注册("bill")
* 如果系统是Linux,给容器中注册("linux")
*/
@Conditional({WindowsCondition.class})
@Bean("bill")
public Person person01(){
return new Person("Bill Gates", 62);
}
@Conditional({LinuxCondition.class})
@Bean("linux")
public Person person02(){
return new Person("Linux", 48);
}
测试:
@Test
public void test5(){
String[] namesForType = applicationContext.getBeanNamesForType(Person.class);
Environment environment = applicationContext.getEnvironment();
//动态获取环境变量的值:Windows 10
String property = environment.getProperty("os.name");
System.out.println(property);
for (String name :
namesForType ) {
System.out.println(name);
}
Map<String, Person> persons = applicationContext.getBeansOfType(Person.class);
System.out.println(persons);
}
Windows 10
person
bill
给容器中添加Pserson...
{person=Person{name='张三', age=5}, bill=Person{name='Bill Gates', age=62}}
也可以把@Conditional()
放在类上:
@Configuration
//类中组件统一设置:满足当前条件,这个类中配置的所有bean注册才能生效
@Conditional({WindowsCondition.class})
public class MainConfig2 {
......
类中组件统一设置:满足当前条件,这个类中配置的所有bean注册才能生效。
7、使用@Import注解给容器中快速导入一个组件
7.1、@Import(要导入的组件)
创建两个类Color和Red:
public class Color {
}
public class Red {
}
在配置类上配置注解@Import
:
@Configuration
//类中组件统一设置:满足当前条件,这个类中配置的所有bean注册才能生效
@Conditional({WindowsCondition.class})
//@Import导入组件,id默认是组件的全类名
@Import({Color.class, Red.class})
public class MainConfig2 {
......
测试,抽取出打印Bean名称的方法:
private void printBeans(ApplicationContext applicationContext){
String[] definitionNames = applicationContext.getBeanDefinitionNames();
for (String name :
definitionNames) {
System.out.println(name);
}
}
@Test
public void testImport(){
printBeans(applicationContext);
}
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
mainConfig2
bean.Color
bean.Red
person
bill
7.2、在@Import注解中使用ImportSelector接口导入bean
查看@Import
源码:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
/**
* {@link Configuration @Configuration}, {@link ImportSelector},
* {@link ImportBeanDefinitionRegistrar}, or regular component classes to import.
*/
Class<?>[] value();
}
除了regular component classes
常规的类,还可以使用ImportSelector
和ImportBeanDefinitionRegistrar
。
ImportSelector
接口源码:
public interface ImportSelector {
/**
* Select and return the names of which class(es) should be imported based on
* the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
* @return the class names, or an empty array if none
*/
String[] selectImports(AnnotationMetadata importingClassMetadata);
/**
* Return a predicate for excluding classes from the import candidates, to be
* transitively applied to all classes found through this selector's imports.
* <p>If this predicate returns {@code true} for a given fully-qualified
* class name, said class will not be considered as an imported configuration
* class, bypassing class file loading as well as metadata introspection.
* @return the filter predicate for fully-qualified candidate class names
* of transitively imported configuration classes, or {@code null} if none
* @since 5.2.4
*/
@Nullable
default Predicate<String> getExclusionFilter() {
return null;
}
}
ImportSelector接口中有一个selectImports()
方法,因此需要重写该方法。@return the class names, or an empty array if none
表明:返回值是一个class数组或一个空数组。
再创建两个类,Blue和Yellow:
public class Blue {
}
public class Yellow {
}
创建一个MyImportSelector类,实现ImportSelector接口:
//自定义逻辑需要导入的组件
public class MyImportSelector implements ImportSelector {
//返回值,就是导入到容器中的组件全类名
//AnnotationMetadata:当前标注@Import注解的类的所有注解信息
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"bean.Blue", "bean.Yellow"};
}
}
测试:
@Test
public void testImport(){
printBeans(applicationContext);
}
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
mainConfig2
bean.Color
bean.Red
bean.Blue
bean.Yellow
person
bill
7.3、在@Import注解中使用ImportBeanDefinitionRegistrar向容器中注册bean
查看ImportBeanDefinitionRegistrar接口:
public interface ImportBeanDefinitionRegistrar {
/**
* Register bean definitions as necessary based on the given annotation metadata of
* the importing {@code @Configuration} class.
* <p>Note that {@link BeanDefinitionRegistryPostProcessor} types may <em>not</em> be
* registered here, due to lifecycle constraints related to {@code @Configuration}
* class processing.
* <p>The default implementation delegates to
* {@link #registerBeanDefinitions(AnnotationMetadata, BeanDefinitionRegistry)}.
* @param importingClassMetadata annotation metadata of the importing class
* @param registry current bean definition registry
* @param importBeanNameGenerator the bean name generator strategy for imported beans:
* {@link ConfigurationClassPostProcessor#IMPORT_BEAN_NAME_GENERATOR} by default, or a
* user-provided one if {@link ConfigurationClassPostProcessor#setBeanNameGenerator}
* has been set. In the latter case, the passed-in strategy will be the same used for
* component scanning in the containing application context (otherwise, the default
* component-scan naming strategy is {@link AnnotationBeanNameGenerator#INSTANCE}).
* @since 5.2
* @see ConfigurationClassPostProcessor#IMPORT_BEAN_NAME_GENERATOR
* @see ConfigurationClassPostProcessor#setBeanNameGenerator
*/
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,
BeanNameGenerator importBeanNameGenerator) {
registerBeanDefinitions(importingClassMetadata, registry);
}
/**
* Register bean definitions as necessary based on the given annotation metadata of
* the importing {@code @Configuration} class.
* <p>Note that {@link BeanDefinitionRegistryPostProcessor} types may <em>not</em> be
* registered here, due to lifecycle constraints related to {@code @Configuration}
* class processing.
* <p>The default implementation is empty.
* @param importingClassMetadata annotation metadata of the importing class
* @param registry current bean definition registry
*/
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
}
}
ImportBeanDefinitionRegistrar接口中有一个方法:registerBeanDefinitions()
,其中有两个参数:
- AnnotationMetadata importingClassMetadata:当前类的注解信息
- BeanDefinitionRegistry registry:BeanDefinition注册类
查看BeanDefinitionRegistry接口:
public interface BeanDefinitionRegistry extends AliasRegistry {
void registerBeanDefinition(String var1, BeanDefinition var2) throws BeanDefinitionStoreException;
void removeBeanDefinition(String var1) throws NoSuchBeanDefinitionException;
BeanDefinition getBeanDefinition(String var1) throws NoSuchBeanDefinitionException;
boolean containsBeanDefinition(String var1);
String[] getBeanDefinitionNames();
int getBeanDefinitionCount();
boolean isBeanNameInUse(String var1);
}
其中也有一个方法:registerBeanDefinition()
,里边有一个BeanDefinition类型。
public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
String SCOPE_SINGLETON = "singleton";
String SCOPE_PROTOTYPE = "prototype";
int ROLE_APPLICATION = 0;
int ROLE_SUPPORT = 1;
int ROLE_INFRASTRUCTURE = 2;
......
BeanDefinition是一个接口,我们需要找到其实现类,其中有一个RootBeanDefinition:
public class RootBeanDefinition extends AbstractBeanDefinition {
@Nullable
private BeanDefinitionHolder decoratedDefinition;
......
创建MyImportBeanDefinitionRegistrar,继承ImportBeanDefinitionRegistrar接口:
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
/**
*
* @param importingClassMetadata:当前类的注解信息
* @param registry:BeanDefinition注册类
* 把所有需要添加到容器中的bean;调用
* registry.registerBeanDefinition手动注册
*/
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
boolean definition = registry.containsBeanDefinition("bean.Red");
boolean definition1 = registry.containsBeanDefinition("bean.Blue");
if (definition && definition1){
//指定Bean定义信息:Bean的类型,Scope,...
RootBeanDefinition beanDefinition = new RootBeanDefinition(RainBow.class);
//注册一个Bean,指定bean名
registry.registerBeanDefinition("rainBow", beanDefinition);
}
}
}
测试:
@Test
public void testImport(){
printBeans(applicationContext);
}
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
mainConfig2
bean.Color
bean.Red
bean.Blue
bean.Yellow
person
bill
rainBow
总结:
给容器中注册组件:
- 包扫描+组件标记注解(@Controller/@Service/@Repository/@Component)【局限于自己写的类】
- @Bean【导入的第三方包里边的组件】
- @Import【快速给容器中导入组件】
- @Import(要导入的组件):容器中就会自动注册这个组件,id默认是组件的全类名
- ImportSelect:返回需要导入的组件的全类名数组
- ImportBeanDefinitionRegistrar:手动注册Bean到容器中
8、如何使用FactoryBean向Spring容器中注册bean?
查看一下FactoryBean的源码:
public interface FactoryBean<T> {
String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";
@Nullable
T getObject() throws Exception;
@Nullable
Class<?> getObjectType();
default boolean isSingleton() {
return true;
}
}
是一个接口,里边还要三个方法:getObject()
、getObjectType()
、isSingleton()
。
创建一个ColorFactoryBean类,继承FactoryBean接口:
//创建一个Spring定义的FacoryBean
public class ColorFactoryBean implements FactoryBean<Color> {
//返回一个Color对象,这个对象会添加到IOC容器中
public Color getObject() throws Exception {
System.out.println("ColorFactoryBean...调用getObject()");
return new Color();
}
public Class<?> getObjectType() {
return Color.class;
}
//控制是单例吗?
//如果返回true,这个bean是单实例,在容器中保存一份
//如果返回false,这个bean是多实例,每次获取都会创建一个新的bean,就是调用getObject()方法
public boolean isSingleton() {
return false;
}
}
需要重写三个方法:
getObject()
:返回一个对象,这个对象会添加到IOC容器中;getObjectType()
:返回值是一个Class类型;isSingleton()
:是否是单实例?
在配置文件中写上colorFactoryBean()
方法:
@Bean
public ColorFactoryBean colorFactoryBean(){
return new ColorFactoryBean();
}
@Test
public void testImport(){
printBeans(applicationContext);
//工厂Bean获取的是调用getObject创建的对象
Object colorFactoryBean = applicationContext.getBean("colorFactoryBean");
Object colorFactoryBean2 = applicationContext.getBean("colorFactoryBean");
System.out.println("bean类型:"+colorFactoryBean.getClass());
System.out.println(colorFactoryBean == colorFactoryBean2);
Object colorFactoryBean3 = applicationContext.getBean("&colorFactoryBean");
System.out.println(colorFactoryBean3.getClass());
}
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
mainConfig2
bean.Color
bean.Red
bean.Blue
bean.Yellow
person
bill
colorFactoryBean
rainBow
ColorFactoryBean...调用getObject()
ColorFactoryBean...调用getObject()
bean类型:class bean.Color
false
class bean.ColorFactoryBean
可以发现:
-
工厂Bean获取的是调用getObject创建的对象
即:
applicationContext.getBean("colorFactoryBean");
根据id获取的工厂bean,得到的是getObject创建的对象,bean.Color -
获取工厂bean本身,我们需要给id前边加一个&
即:
applicationContext.getBean("&colorFactoryBean");
获得的才是bean.ColorFactoryBean
9、如何使用@Bean注解指定初始化和销毁的方法?
bean的生命周期:
- bean初始化 --> 初始化 --> 销毁的过程
容器管理bean的生命周期:
-
我们可以自动以初始化和销毁方法:
容器在bean进行到当前生命周期的时候来调用自定义的初始化和销毁方法
xml配置文件的方法:
<bean id="person" class="bean.Person" scope="prototype" init-method="" destroy-method="">
<property name="age" value="18"></property>
<property name="name" value="zhangsan"/>
</bean>
创建一个Car类,指定其中构造方法、自定义初始化方法和销毁方法:
public class Car {
public Car() {
System.out.println("Car constructor...");
}
public void init(){
System.out.println("Car init...");
}
public void destory(){
System.out.println("Car destory...");
}
}
创建一个配置类MainConfigOfLifeCycle,使用@Bean
的initMethod和destroyMethod指定初始化和销毁方法:
@Configuration
public class MainConfigOfLifeCycle {
@Bean(initMethod = "init", destroyMethod = "destory")
public Car car(){
return new Car();
}
}
@Test
public void test1(){
System.out.println("容器创建开始...");
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class);
System.out.println("容器创建完成...");
//关闭IOC
applicationContext.close();
}
容器创建开始...
Car constructor...
Car init...
容器创建完成...
Car destory...
如果是多实例:
@Configuration
public class MainConfigOfLifeCycle {
@Scope("prototype")
@Bean(initMethod = "init", destroyMethod = "destory")
public Car car(){
return new Car();
}
}
@Test
public void test1(){
System.out.println("容器创建开始...");
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class);
System.out.println("容器创建完成...");
//关闭IOC
applicationContext.close();
}
容器创建开始...
容器创建完成...
可以发现:多实例时,容器创建时是不会创建对象的。
@Test
public void test1(){
System.out.println("容器创建开始...");
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class);
System.out.println("容器创建完成...");
Object car = applicationContext.getBean("car");
//关闭IOC
applicationContext.close();
}
容器创建开始...
容器创建完成...
Car constructor...
Car init...
可以发现:多实例时,获取对象的时候才创建对象,但是在容器关闭时,并没有销毁对象。
10、使用InitializingBean和DisposableBean来管理bean的生命周期
先来查看一下InitializingBean和DisposableBean的源代码:
public interface InitializingBean {
void afterPropertiesSet() throws Exception;
}
一个接口,里边有afterPropertiesSet()
方法。
public interface DisposableBean {
void destroy() throws Exception;
}
也是一个接口,里边有destroy()
方法。
创建一个Car类,实现这两个接口:
@Component
public class Cat implements InitializingBean, DisposableBean {
public Cat() {
System.out.println("Cat constructor...");
}
public void destroy() throws Exception {
System.out.println("Cat destroy...");
}
public void afterPropertiesSet() throws Exception {
System.out.println("Cat afterPropertiesSet...");
}
}
并在其上加上@Component
组件,准备使用扫描的方式:
在配置类上加上扫描的配置:
@ComponentScan("bean")
@Configuration
public class MainConfigOfLifeCycle {
......
测试:
@Test
public void test2(){
System.out.println("容器创建开始...");
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class);
System.out.println("容器创建完成...");
//关闭IOC
applicationContext.close();
}
容器创建开始...
Cat constructor...
Cat afterPropertiesSet...
容器创建完成...
Cat destroy...
11、@PostConstruct注解和@PreDestroy注解
@PostConstruct
源码:
@Documented
@Retention (RUNTIME)
@Target(METHOD)
public @interface PostConstruct {
}
可以看到这个注解是标注在方法(METHOD)上。@PostConstruct:在bean创建完成并且属性赋值完成,来执行初始化方法。
@PreDestroy
源码:
@Documented
@Retention (RUNTIME)
@Target(METHOD)
public @interface PreDestroy {
}
可以看到这个注解也是标注在方法(METHOD)上。@PreDestroy:在容器销毁bean之前通知我们进行清理工作。
创建一个Dog类,添加上面两个注解:
@Component
public class Dog {
public Dog(){
System.out.println("Dog constructor...");
}
//对象创建并赋值之后调用
@PostConstruct
public void init(){
System.out.println("Dog PostConstruct...");
}
//容器移除之前
@PreDestroy
public void destory(){
System.out.println("Dog PreDestroy...");
}
}
12、BeanPostProcessor后置处理器
BeanPostProcessor源码:
public interface BeanPostProcessor {
@Nullable
default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Nullable
default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
有两个方法:
postProcessBeforeInitialization()
:在初始化之前postProcessAfterInitialization()
:在初始化之后
创建MyBeanPostProcessor类继承BeanPostProcessor接口:
@Component
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;
}
}
@Test
public void test2(){
System.out.println("容器创建开始...");
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class);
System.out.println("容器创建完成...");
//关闭IOC
applicationContext.close();
}
容器创建开始...
postProcessBeforeInitializationmainConfigOfLifeCycle==>config.MainConfigOfLifeCycle$$EnhancerBySpringCGLIB$$ba2fd071@4149c063
postProcessAfterInitializationmainConfigOfLifeCycle==>config.MainConfigOfLifeCycle$$EnhancerBySpringCGLIB$$ba2fd071@4149c063
Cat constructor...
postProcessBeforeInitializationcat==>bean.Cat@376a0d86
Cat afterPropertiesSet...
postProcessAfterInitializationcat==>bean.Cat@376a0d86
容器创建完成...
Cat destroy...
13、BeanPostProcessor的执行流程
…
14、BeanPostProcessor在Spring底层是如何使用的?
…
15、使用@Value注解为bean的属性赋值
创建一个MyConfigOfPropertyValues配置类:
@Configuration
public class MyConfigOfPropertyValues {
@Bean
public Person person(){
return new Person();
}
}
在Person类中使用@Value为属性赋值:
public class Person {
//使用@Value赋值:
//1、基本数值
//2、可以写SqEL:#{}
//3、可以写${}:取出配置文件中的值(在运行环境变量里边的值)
@Value("王五")
private String name;
private Integer age;
......
测试:
public class IOCTest_PropertyValue {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfigOfPropertyValues.class);
private void printBeans(ApplicationContext applicationContext){
String[] definitionNames = applicationContext.getBeanDefinitionNames();
for (String name :
definitionNames) {
System.out.println(name);
}
}
@Test
public void test1(){
printBeans(applicationContext);
System.out.println("=======================");
Object person = applicationContext.getBean("person");
System.out.println(person);
//关闭IOC
applicationContext.close();
}
}
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
myConfigOfPropertyValues
person
=======================
Person{name='王五', age=null}
后两种使用情况:
首先创建一个外部配置文件person.properties:
person.nickName = jack
在Person中添加一个属性nickName:
public class Person {
//使用@Value赋值:
//1、基本数值
//2、可以写SqEL:#{}
//3、可以写${}:取出配置文件[properties]中的值(在运行环境变量里边的值)
@Value("王五")
private String name;
@Value("#{20-2}")
private Integer age;
@Value("${person.nickName}")
private String nickName;
......
在其上使用@Value("${person.nickName}")
获取外部的值。
同时还需要在配置类中获取配置文件,使用@PropertySource
注解:
@PropertySource(value = {"classpath:/person.properties"}, encoding = "GBK")
@Configuration
public class MyConfigOfPropertyValues {
@Bean
public Person person(){
return new Person();
}
}
测试:
@Test
public void test1(){
printBeans(applicationContext);
System.out.println("=======================");
Object person = applicationContext.getBean("person");
System.out.println(person);
//关闭IOC
applicationContext.close();
}
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
myConfigOfPropertyValues
person
=======================
Person{name='王五', age=18, nickName='jack'}
一旦读取到了配置文件,就会加载到环境中,因此可以用environment.getProperty()
方法获取到:
@Test
public void test1(){
printBeans(applicationContext);
System.out.println("=======================");
Object person = applicationContext.getBean("person");
System.out.println(person);
ConfigurableEnvironment environment = applicationContext.getEnvironment();
String property = environment.getProperty("person.nickName");
System.out.println(property);
//关闭IOC
applicationContext.close();
}
16、使用@Autowired、@Qualifier、@Primary这三大注解自动装配组件
自动装配:
Spring利用依赖注入(DI),完成IOC容器的各个组件的依赖关系赋值
16.1、@Autowired:自动注入
简而言之:一个组件要用到另一个组件
就比如BookService组件需要使用到BookDao组件:
@Service
public class BookService {
@Autowired
private BookDao bookDao;
@Override
public String toString() {
return "BookService{" +
"bookDao=" + bookDao +
'}';
}
}
BookController组件需要用到BookService组件:
@Controller
public class BookController {
@Autowired
private BookService bookService;
}
@Test
public void test1(){
printBeans(applicationContext);
System.out.println("=======================");
BookService bookService = applicationContext.getBean(BookService.class);
System.out.println(bookService);
Object bookDao = applicationContext.getBean("bookDao");
System.out.println(bookDao);
//关闭IOC
applicationContext.close();
}
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
mainConfigOfAutowired
bookService
bookDao
bookController
bookDao2
=======================
BookService{bookDao=BookDao@4de5031f}
BookDao@4de5031f
@Autowired
默认优先按照类型去容器中找对应的组件:applicationContext.getBean(BookService.class);
找到就赋值
那么如果容器中有两个组件相同类型的组件,@Autowired
会选择哪一个呢?
除了自动注入的,我们在配置类中注册一个组件:
@Configuration
@ComponentScan({"service","dao","controller"})
public class MainConfigOfAutowired {
@Bean("bookDao2")
public BookDao bookDao(){
BookDao bookDao = new BookDao();
bookDao.setLabel("2");
return bookDao;
}
}
这个bookDao的id我们取为bookDao2。而自动注入的是首字母小写,即bookDao。
同时为了更明显的区分,我们在BookDao类中添加一个label属性,默认为1:
@Repository
public class BookDao {
private String label = "1";
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
public BookDao() {
}
@Override
public String toString() {
return "BookDao{" +
"label='" + label + '\'' +
'}';
}
}
测试:
@Test
public void test1(){
printBeans(applicationContext);
System.out.println("=======================");
BookService bookService = applicationContext.getBean(BookService.class);
System.out.println(bookService);
Object bookDao = applicationContext.getBean("bookDao");
System.out.println(bookDao);
//关闭IOC
applicationContext.close();
}
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
mainConfigOfAutowired
bookService
bookDao
bookController
bookDao2
=======================
BookService{bookDao=BookDao{label='1'}}
BookDao{label='1'}
可以发现自动注入的label为1的bookDao,即自动扫描注入的bookDao。
那么如何装配bookDao2呢?
@Service
public class BookService {
@Autowired
private BookDao bookDao2;
@Override
public String toString() {
return "BookService{" +
"bookDao=" + bookDao2 +
'}';
}
}
把BookService中的BookDao属性改为bookDao2。
测试:
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
mainConfigOfAutowired
bookService
bookDao
bookController
bookDao2
=======================
BookService{bookDao=BookDao{label='2'}}
BookDao{label='1'}
16.2、@Qualifier:指定id装配
@Service
public class BookService {
@Qualifier("bookDao")
@Autowired
private BookDao bookDao2;
@Override
public String toString() {
return "BookService{" +
"bookDao=" + bookDao2 +
'}';
}
}
测试:
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
mainConfigOfAutowired
bookService
bookDao
bookController
bookDao2
=======================
BookService{bookDao=BookDao{label='1'}}
BookDao{label='1'}
那如果自动装配的对象没有注册到容器中呢?
我们把BookDao从容器中删去:
//@Repository
public class BookDao {
......
@Configuration
@ComponentScan({"service","dao","controller"})
public class MainConfigOfAutowired {
//@Bean("bookDao2")
public BookDao bookDao(){
......
再次测试:
Error creating bean with name 'bookService': Unsatisfied dependency expressed through field 'bookDao2'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'dao.BookDao' available:
会报错!
所以,自动装配默认是一定要将属性赋值好,没有就会报错!
可以使用@Autowired中的required属性改变自动装配策略:如果容器中存在组件则自动装配,没有则不装配。
@Service
public class BookService {
@Qualifier("bookDao")
@Autowired(required = false)
private BookDao bookDao2;
@Override
public String toString() {
return "BookService{" +
"bookDao=" + bookDao2 +
'}';
}
}
测试:
@Test
public void test1(){
printBeans(applicationContext);
System.out.println("=======================");
BookService bookService = applicationContext.getBean(BookService.class);
System.out.println(bookService);
//关闭IOC
applicationContext.close();
}
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
mainConfigOfAutowired
bookService
bookController
=======================
BookService{bookDao=null}
可以看到,bookDao为null。
16.3、@Primary:默认首选的bean
让Spring进行自动装配的时候,默认首选的bean。
@Primary
@Bean("bookDao2")
public BookDao bookDao(){
BookDao bookDao = new BookDao();
bookDao.setLabel("2");
return bookDao;
}
比如说,在bookDao2上添加注解@Primary
:就是在装配的时候,首选是bookDao2。
这样的话,@Qualifier
注解就不能使用了,这个是明确指定id装配。与属性名也无关:
//@Qualifier("bookDao")
@Autowired(required = false)
private BookDao bookDao;
测试:
BookService{bookDao=BookDao{label='2'}}
BookDao{label='1'}
装配的是bookDao2。当然,还可以继续使用@Qualifier
明确指定装配哪个:
@Qualifier("bookDao")
@Autowired(required = false)
private BookDao bookDao;
测试:
BookService{bookDao=BookDao{label='1'}}
BookDao{label='1'}
可以看到虽然bookDao2是默认首先,但是我使用@Qualifier
明确指定要装配bookDao。
17、@Resource注解和@Inject注解
Spring还支持使用@Resource
(JSR250)注解和@Inject
注解(JSR330),这两个都是java规范的注解。
@Resource
:
-
可以和
@Autowired
一样实现自动装配功能,默认是按照组件名称进行装配的。没有能支持
@Primary
功能和@Autowired(required = false)
的功能。
@Service
public class BookService {
//@Qualifier("bookDao")
//@Autowired(required = false)
@Resource
private BookDao bookDao;
@Override
public String toString() {
return "BookService{" +
"bookDao=" + bookDao +
'}';
}
}
在BookService中把@Autowired(required = false)
和@Qualifier("bookDao")
注解注释掉,添加@Resource
注解。
BookService{bookDao=BookDao{label='1'}}
BookDao{label='1'}
@Primary
注解的是bookDao2,但是并没有注入。
使用@Inject
还需要导入一个依赖:
<!-- https://mvnrepository.com/artifact/javax.inject/javax.inject -->
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
@Service
public class BookService {
//@Qualifier("bookDao")
//@Autowired(required = false)
//@Resource
@Inject
private BookDao bookDao;
......
BookService{bookDao=BookDao{label='2'}}
BookDao{label='1'}
@Inject
:
- 需要导入javax.inject的包,和
@Autowired
的功能一样。 - 没有
required = false
功能。
18、实现方法、构造器位置的自动装配
@Autowired
源码:
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
boolean required() default true;
}
@Autowired
可以标注的位置:
- CONSTRUCTOR:构造器
- METHOD:方法
- PARAMETER:参数
- FIELD:属性
18.1、方法位置
创建一个Boss类:
@Component
public class Boss {
private Car car;
public Car getCar() {
return car;
}
//标注在方法上,Spring容器创建当前对象,就会调用方法,完成赋值
//方法使用的参数,自定义类型的值从ioc容器上获取
@Autowired
public void setCar(Car car) {
this.car = car;
}
@Override
public String toString() {
return "Boss{" +
"car=" + car +
'}';
}
}
在setCar()
方法上添加@Autowired
:
- 标注在方法上,Spring容器创建当前对象,就会调用方法,完成赋值
- 方法使用的参数,自定义类型的值从ioc容器上获取
测试:
@Test
public void test2(){
Object boss = applicationContext.getBean("boss");
System.out.println(boss);
//关闭IOC
applicationContext.close();
}
Boss{car=bean.Car@130161f7}
Cat destroy...
那么,这个Car是从容器中拿到的吗?
@Test
public void test2(){
System.out.println("=======================");
Object boss = applicationContext.getBean("boss");
System.out.println(boss);
Car car = applicationContext.getBean(Car.class);
System.out.println(car);
//关闭IOC
applicationContext.close();
}
Boss{car=bean.Car@130161f7}
bean.Car@130161f7
Cat destroy...
发现没错,地址相同。
18.2、构造器位置
@Component
public class Boss {
......
默认加在ioc容器组件,容器自动会调用无参构造器,创建对象,再进行初始化赋值操作。
我们给Boss添加一个有参构造器:
@Component
public class Boss {
private Car car;
public Boss(Car car) {
this.car = car;
}
......
给有参构造器添加@Autowired
组件:
@Autowired
public Boss(Car car) {
this.car = car;
System.out.println("Boss...有参构造器");
}
那么有参构造器要使用的参数也是从容器中获取的吗?下面做验证:
Boss{car=bean.Car@50b472aa}
bean.Car@50b472aa
没错!就是从容器中获取。
如果组件只有一个有参构造器,这个有参构造器的@Autowired
可以省略。参数位置的组件还是从容器中获取。
18.3、参数
public Boss(@Autowired Car car) {
this.car = car;
System.out.println("Boss...有参构造器");
}
也是从容器中获取参数的值。
18.4、方法位置
假如Color也需要Car:
public class Color {
private Car car;
public Color() {
}
......
我们在配置类中使用方法为容器中注入组件Color:
@Bean
public Color color(Car car){
return new Color();
}
@Bean
标注的方法创建对象的时候,方法参数的值从容器中获取。
默认不写@Autowired
。
19、自定义组件中如何注入Spring底层的组件?
…
20、使用@Profile注解实现开发、测试和生产环境的配置和切换
@Profile
:
- Spring为我们提供的可以根据当前环境,动态激活和切换一系列组件的功能;
比如:
- 开发环境,想要连接A数据源
- 测试环境,想要连接B数据源
- 生产环境,想要连接C数据源
以数据源为例:
首先导入需要的包:
<!-- https://mvnrepository.com/artifact/com.mchange/c3p0 -->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
然后创建配置类,创建三个不同的数据源:
@Configuration
public class MainConfigOfProfile {
@Bean
public DataSource dataSourceTest() throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser("root");
dataSource.setPassword("Opfordream@0518");
dataSource.setJdbcUrl("jdbc:mysql://192.168.196.128:3306/mysql");
dataSource.setDriverClass("com.mysql.jdbc.Driver");
return dataSource;
}
@Bean
public DataSource dataSourceDev() throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser("root");
dataSource.setPassword("Opfordream@0518");
dataSource.setJdbcUrl("jdbc:mysql://192.168.196.128:3306/ssm_crud");
dataSource.setDriverClass("com.mysql.jdbc.Driver");
return dataSource;
}
@Bean
public DataSource dataSourceProd() throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser("root");
dataSource.setPassword("Opfordream@0518");
dataSource.setJdbcUrl("jdbc:mysql://192.168.196.128:3306/db1");
dataSource.setDriverClass("com.mysql.jdbc.Driver");
return dataSource;
}
}
但一般不写死,创建一个properties文件:
db.user=root
db.password=Opfordream@0518
db.driverClass=com.mysql.cj.jdbc.Driver
@PropertySource("classpath:/dbconfig.properties")
@Configuration
public class MainConfigOfProfile {
@Value("${db.user}")
private String user;
@Value("${db.driverClass}")
private String driverClass;
@Bean("testDataSource")
public DataSource dataSourceTest(@Value("${db.password}") String pwd) throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser(user);
dataSource.setPassword(pwd);
dataSource.setJdbcUrl("jdbc:mysql://192.168.196.128:3306/mysql");
dataSource.setDriverClass(driverClass);
return dataSource;
}
@Bean("devDataSource")
public DataSource dataSourceDev(@Value("${db.password}") String pwd) throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser(user);
dataSource.setPassword(pwd);
dataSource.setJdbcUrl("jdbc:mysql://192.168.196.128:3306/ssm_crud");
dataSource.setDriverClass(driverClass);
return dataSource;
}
@Bean("prodDataSource")
public DataSource dataSourceProd(@Value("${db.password}") String pwd) throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser(user);
dataSource.setPassword(pwd);
dataSource.setJdbcUrl("jdbc:mysql://192.168.196.128:3306/db1");
dataSource.setDriverClass(driverClass);
return dataSource;
}
}
测试容器中有哪些数据源:
@Test
public void test1(){
AnnotationConfigApplicationContext applicationContext =
new AnnotationConfigApplicationContext(MainConfigOfProfile.class);
String[] beanNamesForType =
applicationContext.getBeanNamesForType(DataSource.class);
for (String name :
beanNamesForType) {
System.out.println(name);
}
applicationContext.close();
}
testDataSource
devDataSource
prodDataSource
有三个数据源。
@Profile
:
- 指定组件在哪个环境下才能被注册到容器中。
@Profile("test")
@Bean("testDataSource")
public DataSource dataSourceTest(@Value("${db.password}") String pwd) throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser(user);
dataSource.setPassword(pwd);
dataSource.setJdbcUrl("jdbc:mysql://192.168.196.128:3306/mysql");
dataSource.setDriverClass(driverClass);
return dataSource;
}
@Profile("dev")
@Bean("devDataSource")
public DataSource dataSourceDev(@Value("${db.password}") String pwd) throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser(user);
dataSource.setPassword(pwd);
dataSource.setJdbcUrl("jdbc:mysql://192.168.196.128:3306/ssm_crud");
dataSource.setDriverClass(driverClass);
return dataSource;
}
@Profile("prod")
@Bean("prodDataSource")
public DataSource dataSourceProd(@Value("${db.password}") String pwd) throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser(user);
dataSource.setPassword(pwd);
dataSource.setJdbcUrl("jdbc:mysql://192.168.196.128:3306/db1");
dataSource.setDriverClass(driverClass);
return dataSource;
}
再次测试,发现一个组件都没有了。
加了环境标识的bean,只有这个环境被激活的时候才能注册到容器中。默认是default环境。
如果在测试环境下,还有一个组件Yellow:
@Profile("test")
@Bean
public Yellow yellow(){
return new Yellow();
}
那么如何切换环境呢?
-
使用命令行动态参数
-
代码方式
@Test public void test1(){ //1、创建一个applicationContext AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(); //2、设置需要激活的环境 applicationContext.getEnvironment().setActiveProfiles("test", "dev"); //3、注册主配置类 applicationContext.register(MainConfigOfProfile.class); //4、启动刷新容器 applicationContext.refresh(); String[] beanNamesForType = applicationContext.getBeanNamesForType(DataSource.class); for (String name : beanNamesForType) { System.out.println(name); } applicationContext.close(); }
testDataSource devDataSource
-
@Profile
还可以写在配置类上只有当前环境是指定环境是,该类中的组件才能注册。
-
没有标注环境标识的bean,在任何环境下都是加载的。