在Spring3.0之前,我们的bean一直通过XML文件来配置的,后来在Spring3.0之后为我们提供了java的config版本。而且在Spring4.0之后推荐我们使用,这篇文章基于Spring5.2来分析。希望在平时使用和面试的时候对你有一丝帮助。

一、实例演示

我们先给出一个例子,看看如何使用,然后再来分析。创建一个普通的maven项目。

第一步:添加pom依赖

 1<dependencies>
2        <dependency>
3            <groupId>org.springframework</groupId>
4            <artifactId>spring-core</artifactId>
5             <version>5.2.2.RELEASE</version>
6        </dependency>
7        <dependency>
8            <groupId>org.springframework</groupId>
9            <artifactId>spring-beans</artifactId>
10            <version>5.2.2.RELEASE</version>
11        </dependency>
12        <dependency>
13            <groupId>org.springframework</groupId>
14            <artifactId>spring-context</artifactId>
15            <version>5.2.2.RELEASE</version>
16        </dependency>
17        <dependency>
18            <groupId>org.junit.jupiter</groupId>
19            <artifactId>junit-jupiter-api</artifactId>
20            <version>5.3.2</version>
21            <scope>test</scope>
22        </dependency>
23</dependencies>

这里面添加了最核心的依赖。

第二步:在bean包添加User类

 1public class User {
2    private Integer id;
3    private String name;
4    private String password;
5    public User() {
6    }
7    public User(Integer id, String name, String password) {
8        this.id = id;
9        this.name = name;
10        this.password = password;
11    }
12    //setter和getter方法
13    //toString方法
14}

第三步:在service包添加UserService类

 1public class UserService {
2    public List<User> queryAll(){
3        //创建一个新的list集合,用来承接数据,充当返回值
4        List<User> users=new ArrayList<User>();
5        //添加数据name,pass
6        for (int i=0;i<10;i++){
7            User user = new User();
8            user.setId(i);
9            user.setName("name==>"+i);
10            user.setPassword("pass==>"+i);
11            users.add(user);
12        }
13        return users;
14    }
15}

注意这里没有@Service注解。目的就是我们自己注入。

第四步:在config包添加UserConfig类

1@Configuration
2public class UserConfig {
3    @Bean
4    public UserService userService(){
5        return new UserService();
6    }
7}

这个类是核心,我们使用了俩注解,一个Configuration还有一个Bean。我们一会就看他俩的作用。

第五步:测试

之前我们已经添加了测试依赖。直接测试一波:

 1public class MyConfigTest {
2    @Test
3    public  void test(){
4        //获取java配置类
5        AnnotationConfigApplicationContext context =
6                new AnnotationConfigApplicationContext(UserConfig.class);
7        //获取ioc容器中的对象
8        UserService userService = (UserService) context.getBean("userService");
9        //调用方法
10        List<User> query = userService.queryAll();
11        for (User user:query) {
12            System.out.println(user);
13        }
14    }
15}
16User{id=0, name='name==>0', password='pass==>0'}
17...
18User{id=8, name='name==>8', password='pass==>8'}
19User{id=9, name='name==>9', password='pass==>9'}

这就是一个最基本的案例,实现起来非常的简单。下面我们着重分析一下这俩注解的作用,为什么能实现类似于Spring中XML文件一样的作用。

二、分析

1、注解角度分析

想要了解为什么@Configuration会有这样的作用,我们可以跟进去这个注解看看。

 1@Target({ElementType.TYPE})
2@Retention(RetentionPolicy.RUNTIME)
3@Documented
4@Component
5public @interface Configuration {
6    @AliasFor(
7        annotation = Component.class
8    )
9    String value() default "";
10    boolean proxyBeanMethods() default true;
11}

进去之后我们会发现,这个注解标签是一个元注解,由很多其他的注解实现,有一个我们应该很熟悉,那就是@Component,有着了这个注解就可以被@ComponentScan扫描并处理。Spring5.0已经自动扫描了,不需要我们自己再去添加了。现在我们进去到里面的代码看看:

首先是@AliasFor标签:

在Spring的众多注解中,经常会发现很多注解的不同属性起着相同的作用,比如@RequestMapping的value属性和path属性,这就需要做一些基本的限制,比如value和path的值不能冲突,比如任意设置value或者设置path属性的值,都能够通过另一个属性来获取值等等。为了统一处理这些情况,Spring创建了@AliasFor标签。

然后是value() :

意思是默认的值就是空,此时我们就可以指定@Configuration(value="属性值")的这种方式,因为只有一个value所以value可以省去不写。

最后是proxyBeanMethods:

有了 proxyBeanMethods 属性后,配置类不会被代理了。主要是为了提高性能,如果你的 @Bean 方法之间没有调用关系的话可以把 proxyBeanMethods 设置为 false。否则,方法内部引用的类生产的类和 Spring 容器中类是两个类。

3、运行角度分析

现在我们把目光转移,从测试运行的角度来分析。

1//获取java配置类
2AnnotationConfigApplicationContext context =
3       new AnnotationConfigApplicationContext(UserConfig.class);
4//获取ioc容器中的对象
5UserService userService = (UserService) context.getBean("userService");

看到这里,可能就要深入到Spring的源码中看了。Spring容器启动时,ApplicationContext接口的实现类AnnotationConfigApplicationContext会执行refresh方法,往BeanFactory注册bean就在此方法完成。我们看到这个refresh是核心。我们进入到这个源码中看看:

 1public void refresh() throws BeansException, IllegalStateException {
2    synchronized (this.startupShutdownMonitor) {
3        //准备刷新的上下文 环境  
4        prepareRefresh();
5        //初始化BeanFactory,并进行XML文件读取  
6        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
7        //对beanFactory进行各种功能填充  
8        prepareBeanFactory(beanFactory);
9        try {
10            //子类覆盖方法做额外处理  
11            postProcessBeanFactory(beanFactory);
12            //激活各种beanFactory处理器  
13            invokeBeanFactoryPostProcessors(beanFactory);
14            //注册拦截Bean创建的Bean处理器,这里只是注册,真正的调用实在getBean时候 
15            registerBeanPostProcessors(beanFactory);
16            //为上下文初始化Message源,即不同语言的消息体,国际化处理  
17            initMessageSource();
18            //初始化应用消息广播器,并放入“applicationEventMulticaster”bean中  
19            initApplicationEventMulticaster();
20            ...
21        }
22        ...
23    }
24}

我截取了其中一部分的源码,在里面有一个方法很关键,那就是invokeBeanFactoryPostProcessors,意思是我们Spring容器首先会初始化BeanFactory,然后激活各种beanFactory处理器,也就是执行invokeBeanFactoryPostProcessors,我们看看这个方法:

 1 public static void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory, 
2       List<BeanFactoryPostProcessor> beanFactoryPostProcessors)
 
{
3     Set<String> processedBeans = new HashSet<>();
4     if (beanFactory instanceof BeanDefinitionRegistry) {
5         // 定义BeanDefinitionRegistryPostProcessor集合
6         List<BeanDefinitionRegistryPostProcessor> registryProcessors = new ArrayList<>();
7         // 循环手动注册的beanFactoryPostProcessors
8         for (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessors) {
9             // 如果是BeanDefinitionRegistryPostProcessor的实例话,则调用其postProcessBeanDefinitionRegistry方法,对bean进行注册操作
10             if (postProcessor instanceof BeanDefinitionRegistryPostProcessor) {
11                // 如果是BeanDefinitionRegistryPostProcessor类型,则直接调用其postProcessBeanDefinitionRegistry
12                BeanDefinitionRegistryPostProcessor registryProcessor = (BeanDefinitionRegistryPostProcessor) postProcessor;
13                registryProcessor.postProcessBeanDefinitionRegistry(registry);
14                registryProcessors.add(registryProcessor);
15            }
16            // 否则则将其当做普通的BeanFactoryPostProcessor处理,直接加入regularPostProcessors集合,以备后续处理
17            else {
18                regularPostProcessors.add(postProcessor);
19            }
20         }
21     }
22     else {
23         invokeBeanFactoryPostProcessors(beanFactoryPostProcessors, beanFactory);
24     }
25 }

在这个方法的内部的核心是ConfigurationClassPostProcessor,这个方法看到@Configuration,就会开启类的加载,这里也就是bean的加载。剩下的越挖越深,源码也越来越深。大体步骤我们可以总结一下:

ConfigurationClassPostProcessor处理器解析@configuration配置类主要过程:

(1)Spring容器初始化时注册ConfigurationClassPostProcessor

(2)Spring容器初始化执行refresh()方法中调用ConfigurationClassPostProcessor

(3)ConfigurationClassPostProcessor处理器借助ConfigurationClassParser完成配置类解析

(4)ConfigurationClassParser配置内解析过程中完成嵌套的MemberClass、@PropertySource注解、@ComponentScan注解(扫描package下的所有Class并进行迭代解析,主要是@Component组件解析及注册)、@ImportResource、@Bean等处理

(5)接下来完成@Bean注册, @ImportResource指定bean的注册以及@Import的bean注册

(6)有@Bean注解的方法在解析的时候作为ConfigurationClass的一个属性,最后还是会转换成BeanDefinition进行处理, 而实例化的时候会作为一个工厂方法进行Bean的创建

现在大致应该明白了,其实一句话说完,还是想办法识别注解,完成和XML一样的功能。


更多相关文章

  1. ConcurrentHashMap之size()方法
  2. 为什么不推荐使用finalize方法,来看看对垃圾回收有什么影响吧
  3. Springboot整合mybatis多数据源(注解完整版)
  4. java8中的一个骚操作-方法引用(使代码看起来很高大上)
  5. Springboot整合mybatis(注解而且能看明白版本)
  6. JS内存泄漏排查方法
  7. 扒一扒 @SpringBootApplication 注解背后的奥秘!

随机推荐

  1. 无法让.click()在禁用的textarea上工作
  2. AJAX应该使用hashtag /#!/或不呢?
  3. Div高度为图像高度,图像宽度为div宽度
  4. 环回 - 在“保存后”挂钩中覆盖之前查看
  5. 获取“RangeError:超出最大调用堆栈大小”
  6. js曲线图+饼状图+柱状图 (json数据)
  7. 基于缓冲区数据创建文件
  8. Javascript setTimeout 带参数延迟执行
  9. Node.js和webcrypto之间的RSA加密
  10. javascript相关小内容