1. Bean的创建

1.1 xml配置

<bean id="xxxx"  class="xxxx.xxxx"/>
1

1.2 注解

@Component

@Service

@Controler

@Repository

1.3 其他注解

@Bean 第三方Bean

@Configuration 声明配置类

1.4 @Import

@Import(User.class)
public class SpringConfig {
}

1
2
3
4
public class App {

    public static void main(String[] args) {
//        ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
        User bean = context.getBean(User.class);
        System.out.println(bean);
        }
1
2
3
4
5
6
7
8

1.5 ImportSelector

使用ImportSelector或者ImportBeanDefinitionRegistrar接口,配合@Import实现

package com.mszlu.domain;

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

public class MyImportSelector implements ImportSelector {

    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {


        return new String[]{User.class.getName()};
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.mszlu.config;

import com.mszlu.domain.MyImportSelector;
import com.mszlu.domain.User;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Configuration
@ComponentScan("com.mszlu")
//@Import(User.class)
@Import(MyImportSelector.class)
public class SpringConfig {
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

Im

package com.mszlu.domain;

import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {

        BeanDefinition beanDefinition =  new RootBeanDefinition(User.class.getName());
        beanDefinitionRegistry.registerBeanDefinition(User.class.getName(),beanDefinition);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.mszlu.config;

import com.mszlu.domain.MyImportBeanDefinitionRegistrar;
import com.mszlu.domain.MyImportSelector;
import com.mszlu.domain.User;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Configuration
@ComponentScan("com.mszlu")
//@Import(User.class)
//@Import(MyImportSelector.class)
@Import(MyImportBeanDefinitionRegistrar.class)
public class SpringConfig {
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

1.6 手动注入

有些场景下需要代码动态注入,以上方式都不适用。这时就需要创建 对象手动注入。

通过DefaultListableBeanFactory注入。

registerSingleton(String beanName,Object object);
1

示例:

package com.mszlu.domain;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;

public class UserRegistrar implements BeanFactoryAware {
    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        DefaultListableBeanFactory listableBeanFactory = (DefaultListableBeanFactory)beanFactory;
        //方式一
//        BeanDefinition beanDefinition = new RootBeanDefinition(User.class);
//        listableBeanFactory.registerBeanDefinition(User.class.getName(),beanDefinition);
        //方式二
        User user = new User();
        listableBeanFactory.registerSingleton(User.class.getName(),user);
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.mszlu.config;

import com.mszlu.domain.MyImportBeanDefinitionRegistrar;
import com.mszlu.domain.MyImportSelector;
import com.mszlu.domain.User;
import com.mszlu.domain.UserRegistrar;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Configuration
@ComponentScan("com.mszlu")
//@Import(User.class)
//@Import(MyImportSelector.class)
//@Import(MyImportBeanDefinitionRegistrar.class)
@Import(UserRegistrar.class)
public class SpringConfig {
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

2. Bean的作用域

在Bean容器启动会读取bean的xml配置文件,然后将xml中每个bean元素分别转换成BeanDefinition对象。在BeanDefinition对象中有scope 属性,就是它控制着bean的作用域。

Spring框架支持5种作用域,有三种作用域是当开发者使用基于web的ApplicationContext的时候才生效的。

作用域描述
单例(singleton)(默认)每一个Spring IoC容器都拥有唯一的一个实例对象
原型(prototype)一个Bean定义,任意多个对象
请求(request)一个HTTP请求会产生一个Bean对象,也就是说,每一个HTTP请求都有自己的Bean实例。只在基于web的Spring ApplicationContext中可用
会话(session)限定一个Bean的作用域为HTTPsession的生命周期。同样,只有基于web的Spring ApplicationContext才能使用
全局会话(global session)限定一个Bean的作用域为全局HTTPSession的生命周期。通常用于门户网站场景,同样,只有基于web的Spring ApplicationContext可用

3. Bean的生命周期管理

输入图片说明

ApplicationContext容器中,Bean的生命周期流程如上图所示,流程大致如下:

  1. 首先容器启动后,会对scope为singleton且非懒加载的bean进行实例化
  2. 按照Bean定义信息配置信息,注入所有的属性
  3. 如果Bean实现了BeanNameAware接口,会回调该接口的setBeanName()方法,传入该Bean的id,此时该Bean就获得了自己在配置文件中的id
  4. 如果Bean实现了BeanFactoryAware接口,会回调该接口的setBeanFactory()方法,传入该Bean的BeanFactory,这样该Bean就获得了自己所在的BeanFactory
  5. 如果Bean实现了ApplicationContextAware接口,会回调该接口的setApplicationContext()方法,传入该Bean的ApplicationContext,这样该Bean就获得了自己所在的ApplicationContext
  6. 如果有Bean实现了BeanPostProcessor接口,则会回调该接口的postProcessBeforeInitialzation()方法
  7. 如果Bean实现了InitializingBean接口,则会回调该接口的afterPropertiesSet()方法
  8. 如果Bean配置了init-method方法,则会执行init-method配置的方法
  9. 如果有Bean实现了BeanPostProcessor接口,则会回调该接口的postProcessAfterInitialization()方法
  10. 经过流程9之后,就可以正式使用该Bean了,对于scope为singleton的Bean,Spring的ioc容器中会缓存一份该bean的实例,而对于scope为prototype的Bean,每次被调用都会new一个新的对象,生命周期就交给调用方管理了,不再是Spring容器进行管理了
  11. 容器关闭后,如果Bean实现了DisposableBean接口,则会回调该接口的destroy()方法,
  12. 如果Bean配置了destroy-method方法,则会执行destroy-method配置的方法,至此,整个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
        https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.mszlu" />
<!--    <bean id="userService" class="com.mszlu.service.UserService">-->
<!--        <property name="mailService" ref="mailService" />-->
<!--    </bean>-->

<!--    <bean id="mailService" name="mailService2" class="com.mszlu.service.MailService" scope="singleton"/>-->

    <bean id="person1" destroy-method="myDestroy"
          init-method="myInit" class="com.mszlu.domain.Person">
        <property name="name">
            <value>jack</value>
        </property>
    </bean>

    <!-- 配置自定义的后置处理器 -->
    <bean id="postProcessor" class="com.mszlu.domain.MyBeanPostProcessor" />
</beans>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.mszlu.domain;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.*;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

public class Person implements BeanNameAware, BeanFactoryAware,
        ApplicationContextAware, InitializingBean, DisposableBean {

    private String name;

    public Person() {
        System.out.println("Person类构造方法");
    }


    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
        System.out.println("set方法被调用");
    }

    //自定义的初始化函数
    public void myInit() {
        System.out.println("myInit被调用");
    }

    //自定义的销毁方法
    public void myDestroy() {
        System.out.println("myDestroy被调用");
    }

    public void destroy() throws Exception {
        // TODO Auto-generated method stub
        System.out.println("destory被调用");
    }

    public void afterPropertiesSet() throws Exception {
        // TODO Auto-generated method stub
        System.out.println("afterPropertiesSet被调用");
    }

    public void setApplicationContext(ApplicationContext applicationContext)
            throws BeansException {
        // TODO Auto-generated method stub
        System.out.println("setApplicationContext被调用");
    }

    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        // TODO Auto-generated method stub
        System.out.println("setBeanFactory被调用,beanFactory");
    }

    public void setBeanName(String beanName) {
        // TODO Auto-generated method stub
        System.out.println("setBeanName被调用,beanName:" + beanName);
    }

    public String toString() {
        return "name is :" + name;
    }
}
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
package com.mszlu.domain;

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

public class MyBeanPostProcessor implements BeanPostProcessor {

    public Object postProcessBeforeInitialization(Object bean,
                                                  String beanName) throws BeansException {
        // TODO Auto-generated method stub

        System.out.println("postProcessBeforeInitialization被调用");
        return bean;
    }

    public Object postProcessAfterInitialization(Object bean,
                                                 String beanName) throws BeansException {
        // TODO Auto-generated method stub
        System.out.println("postProcessAfterInitialization被调用");
        return bean;
    }
}
 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.mszlu;

import com.mszlu.config.SpringConfig;
import com.mszlu.domain.Person;
import com.mszlu.domain.User;
import com.mszlu.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Import;
import org.springframework.context.support.ClassPathXmlApplicationContext;


public class App {

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
        Person bean = context.getBean(Person.class);
        System.out.println(bean);
        ((ClassPathXmlApplicationContext)context).close();
        //        ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
//        User bean = context.getBean(User.class);
//        System.out.println(bean);
//        UserService userService = (UserService) context.getBean("userService");
//        userService.registerUser("ddd@mszlu.com","123456","ddd");
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

输出:

postProcessBeforeInitialization被调用
postProcessAfterInitialization被调用
postProcessBeforeInitialization被调用
postProcessAfterInitialization被调用
postProcessBeforeInitialization被调用
postProcessAfterInitialization被调用
Person类构造方法
set方法被调用
setBeanName被调用,beanName:person1
setBeanFactory被调用,beanFactory
setApplicationContext被调用
postProcessBeforeInitialization被调用
afterPropertiesSet被调用
myInit被调用
postProcessAfterInitialization被调用
postProcessBeforeInitialization被调用
postProcessAfterInitialization被调用
name is :jack
destory被调用
myDestroy被调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

4. List注入

有些时候,我们会有一系列相同的接口,不同实现类的Bean。例如,注册用户时,我们要对email、password和name这3个变量进行验证。

先定义接口:

public interface Validator {
    void validate(String email, String password, String name);
}
1
2
3

分别使用3个Validator对用户参数进行验证:

@Component
public class EmailValidator implements Validator {
    public void validate(String email, String password, String name) {
        if (!email.matches("^[a-z0-9]+\\@[a-z0-9]+\\.[a-z]{2,10}quot;)) {
            throw new IllegalArgumentException("invalid email: " + email);
        }
    }
}

@Component
public class PasswordValidator implements Validator {
    public void validate(String email, String password, String name) {
        if (!password.matches("^.{6,20}quot;)) {
            throw new IllegalArgumentException("invalid password");
        }
    }
}

@Component
public class NameValidator implements Validator {
    public void validate(String email, String password, String name) {
        if (name == null || name.isBlank() || name.length() > 20) {
            throw new IllegalArgumentException("invalid name: " + name);
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

最后,我们通过一个Validators作为入口进行验证

@Component
public class Validators {
    @Autowired
    List<Validator> validators;

    public void validate(String email, String password, String name) {
        for (var validator : this.validators) {
            validator.validate(email, password, name);
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11

注意到Validators被注入了一个List<Validator>,Spring会自动把所有类型为Validator的Bean装配为一个List注入进来,这样一来,我们每新增一个Validator类型,就自动被Spring装配到Validators中了,非常方便。

因为Spring是通过扫描classpath获取到所有的Bean,而List是有序的,要指定List中Bean的顺序,可以加上@Order注解:

@Component
@Order(1)
public class EmailValidator implements Validator {
    ...
}

@Component
@Order(2)
public class PasswordValidator implements Validator {
    ...
}

@Component
@Order(3)
public class NameValidator implements Validator {
    ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

5. 可选注入

默认情况下,当我们标记了一个@Autowired后,Spring如果没有找到对应类型的Bean,它会抛出NoSuchBeanDefinitionException异常。

可以给@Autowired增加一个required = false的参数:

@Component
public class MailService {
    @Autowired(required = false)
    ZoneId zoneId = ZoneId.systemDefault();
    ...
}
1
2
3
4
5
6

这个参数告诉Spring容器,如果找到一个类型为ZoneId的Bean,就注入,如果找不到,就忽略。

这种方式非常适合有定义就使用定义,没有就使用默认值的情况。