扒一扒Bean注入Spring的那些姿势,你会几种?

简介: 这篇文章我准备来扒一扒Bean注入到Spring的那些姿势。其实关于Bean注入Spring容器的方式网上也有很多相关文章,但是很多文章可能会存在以下常见的问题- 注入方式总结的不全- 没有分析可以使用这些注入方式背后的原因- 没有这些注入方式在源码中的应用示例- ...所以本文就带着解决上述的问题的目的来重新梳理一下Bean注入到Spring的那些姿势。

大家好,我是三友~~

这篇文章我准备来扒一扒Bean注入到Spring的那些姿势。

其实关于Bean注入Spring容器的方式网上也有很多相关文章,但是很多文章可能会存在以下常见的问题

  • 注入方式总结的不全
  • 没有分析可以使用这些注入方式背后的原因
  • 没有这些注入方式在源码中的应用示例
  • ...

所以本文就带着解决上述的问题的目的来重新梳理一下Bean注入到Spring的那些姿势。

配置文件

配置文件的方式就是以外部化的配置方式来声明Spring Bean,在Spring容器启动时指定配置文件。配置文件方式现在用的不多了,但是为了文章的完整性和连续性,这里我还是列出来了,知道的小伙伴可以自行跳过这节。

配置文件的类型Spring主要支持xml和properties两种类型。

xml

在XmlBeanInjectionDemo.xml文件中声明一个class为类型为User的Bean

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       ">

    <bean class="com.sanyou.spring.bean.injection.User"/>

</beans>

User

@Data
@ToString
public class User {
   
   

    private String username;

}

测试:

public class XmlBeanInjectionDemo {
   
   

    public static void main(String[] args) {
   
   
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:XmlBeanInjectionDemo.xml");
        applicationContext.refresh();
        User user = applicationContext.getBean(User.class);
        System.out.println(user);
    }

}

结果:

User(username=null)

可以看出成功将User注入到Spring中,由于没有设置username属性值,所以是null。

properties

除了xml,spring还支持properties配置文件声明Bean的方式。

如下,在PropertiesBeanInjectionDemo.properties文件中声明了class类型为User的Bean,并且设置User的username属性为sanyou。

user.(class) = com.sanyou.spring.bean.injection.User
user.username = sanyou

测试:

public class PropertiesBeanInjectionDemo {
   
   

    public static void main(String[] args) {
   
   
        GenericApplicationContext applicationContext = new GenericApplicationContext();
        //创建一个PropertiesBeanDefinitionReader,可以从properties读取Bean的信息,将读到的Bean信息放到applicationContext中
        PropertiesBeanDefinitionReader propReader = new PropertiesBeanDefinitionReader(applicationContext);
        //创建一个properties文件对应的Resource对象
        Resource classPathResource = new ClassPathResource("PropertiesBeanInjectionDemo.properties");
        //加载配置文件
        propReader.loadBeanDefinitions(classPathResource);
        applicationContext.refresh();
        User user = applicationContext.getBean(User.class);
        System.out.println(user);
    }

}

结果:

User(username=sanyou)

成功获取到User对象,并且username的属性为properties设置的sanyou。

除了可以配置属性之外还支持其它的配置,如何配置可以查看PropertiesBeanDefinitionReader类上的注释。

image.png

注解声明

上一节介绍了通过配置文件的方式来声明Bean,但是配置文件这种方式最大的缺点就是不方便,因为随着项目的不断扩大,可能会产生大量的配置文件。为了解决这个问题,Spring在2.x的版本中开始支持注解的方式来声明Bean。

@Component + @ComponentScan

这种方式其实就不用多说,在项目中自定义的业务类就是通过@Component及其派生注解(@Service、@Controller等)来注入到Spring容器中的。

在SpringBoot环境底下,一般情况下不需要我们主动调用@ComponentScan注解,因为@SpringBootApplication会调用@ComponentScan注解,扫描启动引导类(加了@SpringBootApplication注解的类)所在的包及其子包下所有加了@Component注解及其派生注解的类,注入到Spring容器中。

image.png

@Bean

虽然上面@Component + @ComponentScan的这种方式可以将Bean注入到Spring中,但是有个问题那就是对于第三方jar包来说,如果这个类没加@Component注解,那么@ComponentScan就扫不到,这样就无法注入到Spring容器中,所以Spring提供了一种@Bean的方式来声明Bean。

比如,在使用MybatisPlus的分页插件的时候,就可以按如下方式这么来声明。

@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
   
   
    MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
    interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
    return interceptor;
}

此时就能将MybatisPlusInterceptor这个Bean注入到Spring容器中。

@Import

@Import注解也可以用来将Bean注入到Spring容器中,@Import注解导入的类可以分为三种情况:

  • 普通类
  • 类实现了ImportSelector接口
  • 类实现了ImportBeanDefinitionRegistrar接口
普通类

普通类其实就很简单,就是将@Import导入的类注入到Spring容器中,这没什么好说的。

类实现了ImportSelector接口
public interface ImportSelector {
   
   

    String[] selectImports(AnnotationMetadata importingClassMetadata);

    @Nullable
    default Predicate<String> getExclusionFilter() {
   
   
              return null;
    }

}

当@Import导入的类实现了ImportSelector接口的时候,Spring就会调用selectImports方法的实现,获取一批类的全限定名,最终这些类就会被注册到Spring容器中。

比如如下代码中,UserImportSelector实现了ImportSelector,selectImports方法返回User的全限定名

public class UserImportSelector implements ImportSelector {
   
   

    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
   
   
        System.out.println("调用 UserImportSelector 的 selectImports 方法获取一批类限定名");
        return new String[]{
   
   "com.sanyou.spring.bean.injection.User"};
    }

}

当使用@Import注解导入UserImportSelector这个类的时候,其实最终就会把User注入到Spring容器中,如下测试

@Import(UserImportSelector.class)
public class ImportSelectorDemo {
   
   

    public static void main(String[] args) {
   
   
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        //将 ImportSelectorDemo 注册到容器中
        applicationContext.register(ImportSelectorDemo.class);
        applicationContext.refresh();

        User user = applicationContext.getBean(User.class);
        System.out.println(user);
    }

}

运行结果

User(username=null)

对于类实现了ImportBeanDefinitionRegistrar接口的情况,这个后面说。

一般来说,@Import都是配合@EnableXX这类注解来使用的,比如常见的@EnableScheduling、@EnableAsync注解等,其实最终都是靠@Import来实现的。

image.png

image.png

讲完通过注解的方式来声明Bean之后,可以来思考一个问题,那就是既然注解方式这么简单,为什么Spring还写一堆代码来支持配置文件这种声明的方式?

其实答案很简单,跟Spring的发展历程有关。Spring在创建之初Java还不支持注解,所以只能通过配置文件的方式来声明Bean,在Java1.5版本开始支持注解之后,Spring才开始支持通过注解的方式来声明Bean。

注册BeanDefinition

在说注册BeanDefinition之前,先来聊聊什么是BeanDefinition?

BeanDefinition是Spring Bean创建环节中很重要的一个东西,它封装了Bean创建过程中所需要的元信息。

public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
   
   
    //设置Bean className
    void setBeanClassName(@Nullable String beanClassName);

    //获取Bean className
    @Nullable
       String getBeanClassName();

    //设置是否是懒加载
       void setLazyInit(boolean lazyInit);

    //判断是否是懒加载
       boolean isLazyInit();

    //判断是否是单例
       boolean isSingleton();

}

如上代码是BeanDefinition接口的部分方法,从这方法的定义名称可以看出,一个Bean所创建过程中所需要的一些信息都可以从BeanDefinition中获取,比如这个Bean的class类型,这个Bean是否是懒加载,这个Bean是否是单例的等等,因为有了这些信息,Spring才知道要创建一个什么样的Bean。

有了BeanDefinition这个概念之后,再来看一下配置文件和注解声明这些方式往Spring容器注入Bean的原理。

image.png

如图为Bean注入到Spring大致原理图,整个过程大致分为以下几个步骤

  • 通过BeanDefinitionReader组件读取配置文件或者注解的信息,为每一个Bean生成一个BeanDefinition
  • BeanDefinition生成之后,添加到BeanDefinitionRegistry中,BeanDefinitionRegistry就是用来保存BeanDefinition
  • 当需要创建Bean对象时,会从BeanDefinitionRegistry中拿出需要创建的Bean对应的BeanDefinition,根据BeanDefinition的信息来生成Bean
  • 当生成的Bean是单例的时候,Spring会将Bean保存到SingletonBeanRegistry中,也就是平时说的三级缓存中的第一级缓存中,以免重复创建,需要使用的时候直接从SingletonBeanRegistry中查找

好了,通过以上分析我们知道,配置文件和注解声明的方式其实都是声明Bean的一种方式,最终都会转换成BeanDefinition,Spring是基于BeanDefinition的信息来创建Bean。

既然Spring最终是基于BeanDefinition的信息来创建Bean,那么我们是不是可以跳过配置文件和注解声明的方式,直接通过手动创建和注册BeanDefinition的方式实现往Spring容器中注入呢?

答案是可以的。

前面说过,BeanDefinition最终会被注册到BeanDefinitionRegistry中,那么如何拿到BeanDefinitionRegistry呢?主要有以下两种方式:

  • ImportBeanDefinitionRegistrar
  • BeanDefinitionRegistryPostProcessor

ImportBeanDefinitionRegistrar

上面在说@Import的时候,关于导入的类实现了ImportBeanDefinitionRegistrar接口的情况没有说,主要是因为在这里说比较合适

public interface ImportBeanDefinitionRegistrar {
   
   

      default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,BeanNameGenerator importBeanNameGenerator) {
   
   
             registerBeanDefinitions(importingClassMetadata, registry);
      }

      default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
   
   
      }

}

ImportBeanDefinitionRegistrar中有两个方法,方法的参数就是BeanDefinitionRegistry。当@Import导入的类实现了ImportBeanDefinitionRegistrar接口之后,Spring就会调用registerBeanDefinitions方法,传入BeanDefinitionRegistry。

来个Demo

UserImportBeanDefinitionRegistrar实现ImportBeanDefinitionRegistrar

public class UserImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
   
   

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
   
   
        //构建一个 BeanDefinition , Bean的类型为 User
        AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(User.class)
                //设置User这个Bean的属性username的值为三友的java日记
                .addPropertyValue("username", "三友的java日记")
                .getBeanDefinition();
        //把User的BeanDefinition注入到BeanDefinitionRegistry中
        registry.registerBeanDefinition("user", beanDefinition);
    }

}

测试类

@Import(UserImportBeanDefinitionRegistrar.class)
public class UserImportBeanDefinitionRegistrarDemo {
   
   

    public static void main(String[] args) {
   
   
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        applicationContext.register(UserImportBeanDefinitionRegistrarDemo.class);
        applicationContext.refresh();
        User user = applicationContext.getBean(User.class);
        System.out.println(user);
    }

}

结果

User(username=三友的java日记)

从结果可以看出,成功将User注入到了Spring容器中。

上面的例子中有行代码

applicationContext.register(UserImportBeanDefinitionRegistrarDemo.class);

这行代码的意思就是把UserImportBeanDefinitionRegistrarDemo这个Bean注册到Spring容器中,所以这里其实也算一种将Bean注入到Spring的方式,原理也跟上面一样,会为UserImportBeanDefinitionRegistrarDemo生成一个BeanDefinition注册到Spring容器中。

BeanDefinitionRegistryPostProcessor

除了ImportBeanDefinitionRegistrar可以拿到BeanDefinitionRegistry之外,还可以通过BeanDefinitionRegistryPostProcessor拿到BeanDefinitionRegistry

image.png

这种方式就不演示了。

手动注册BeanDefinition这种方式还是比较常见的。就比如说OpenFeign在启用过程中,会为每个标注了@FeignClient注解的接口创建一个BeanDefinition,然后再往Spring中的注册的,如下是OpenFeign注册FeignClient的部分代码

class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
   
   

    private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
   
   
              //构建BeanDefinition,class类型为FeignClientFactoryBean
              BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);
        String alias = contextId + "FeignClient";
              AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
        BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[] {
   
    alias });
        //注册BeanDefinition
        BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
       }
}

注册创建完成的Bean

上一节说可以跳过配置文件或者是注解,直接通过注册BeanDefinition以达到将Bean注入到Spring中的目的。

既然已经可以跳过配置文件或者是注解,那么我们可不可以更激进一步,跳过注册BeanDefinition这一步,直接往Spring中注册一个已经创建好的Bean呢?

答案依然是可以的。

因为上面在提到当创建的Bean是单例的时候,会将这个创建完成的Bean保存到SingletonBeanRegistry中,需要用到直接从SingletonBeanRegistry中查找。既然最终是从SingletonBeanRegistry中查找的Bean,那么直接注入一个创建好的Bean有什么不可以呢?

既然可以,那么如何拿到SingletonBeanRegistry呢?

其实拿到SingletonBeanRegistry的方法其实很多,因为ConfigurableListableBeanFactory就继承了SingletonBeanRegistry接口,所以只要能拿到ConfigurableListableBeanFactory就相当于拿到了SingletonBeanRegistry。

image.png

而ConfigurableListableBeanFactory可以通过BeanFactoryPostProcessor来获取

image.png

来个Demo

RegisterUserBeanFactoryPostProcessor实现BeanFactoryPostProcessor,
往Spring容器中添加一个手动创建的User对象

public class RegisterUserBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
   
   

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
   
   
        //创建一个User对象
        User user = new User();
        user.setUsername("三友的java日记");
        //将这个User对象注入到Spring容器中
        beanFactory.registerSingleton("user", user);
    }

}

测试

public class RegisterUserDemo {
   
   

    public static void main(String[] args) {
   
   
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        applicationContext.register(RegisterUserBeanFactoryPostProcessor.class);
        applicationContext.refresh();
        User user = applicationContext.getBean(User.class);
        System.out.println(user);
    }

}

结果

User(username=三友的java日记)

从结果还是可以看出,成功从Spring容器中获取到了User对象。

这种直接将创建好的Bean注入到Spring容器中在Spring框架内部使用的还是比较多的,Spring的一些内建的Bean就是通过这个方式注入到Spring中的。

image.png

如上图,在SpringBoot项目启动的过程中会往Spring容器中添加两个创建好的Bean,如果你的程序需要使用到这些Bean,就可以通过依赖注入的方式获取到。

虽然基于这种方式可以将Bean注入到Spring容器,但是这种方式注入的Bean是不经过Bean的生命周期的,也就是说这个Bean中诸如@Autowired等注解和Bean生命周期相关的回调都不会生效的,注入到Spring时Bean是什么样就是什么样,Spring不做处理,仅仅只是做一个保存作用。

FactoryBean

FactoryBean是一种特殊的Bean的类型,通过FactoryBean也可以将Bean注入到Spring容器中。

image.png

当我们通过配置文件、注解声明或者是注册BeanDenifition的方式,往Spring容器中注入了一个class类型为FactoryBean类型的Bean时候,其实真正注入的Bean类型为getObjectType方法返回的类型,并且Bean的对象是通过getObject方法返回的。

来个Demo

UserFactoryBean实现了FactoryBean,getObjectType返回了User类型,所以这个UserFactoryBean会往Spring容器中注入User这个Bean,并且User对象是通过getObject()方法的实现返回的。

public class UserFactoryBean implements FactoryBean<User> {
   
   
    @Override
    public User getObject() throws Exception {
   
   
        User user = new User();
        user.setUsername("三友的java日记");
        return user;
    }

    @Override
    public Class<?> getObjectType() {
   
   
        return User.class;
    }
}

测试

public class UserFactoryBeanDemo {
   
   

    public static void main(String[] args) {
   
   
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        //将UserFactoryBean注入到Spring容器中
        applicationContext.register(UserFactoryBean.class);
        applicationContext.refresh();
        User user = applicationContext.getBean(User.class);
        System.out.println(user);
    }

}

结果

User(username=三友的java日记)

成功通过UserFactoryBean将User这个Bean注入到Spring容器中了。

FactoryBean这中注入的方式使用也是非常多的,就拿上面举例的OpenFeign来说,OpenFeign为每个FeignClient的接口创建的BeanDefinition的Bean的class类型FeignClientFactoryBean就是FactoryBean的实现。

class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean, ApplicationContextAware {
   
   

    // FeignClient接口类型
    private Class<?> type;

    @Override
       public Object getObject() throws Exception {
   
   
             return getTarget();
       }

    @Override
       public Class<?> getObjectType() {
   
   
             return type;
       }
}

getObject()方法就会返回接口的动态代理的对象,并且这个代理对象是由Feign创建的,这也就实现了Feign和Spring的整合。

总结

通过以上分析可以看出,将Bean注入到Spring容器中大致可以分为5类:

  • 配置文件
  • 注解声明
  • 注册BeanDefinition
  • 注册创建完成的Bean
  • FactoryBean

以上几种注入的方式,在日常业务开发中,基本上都是使用注解声明的方式注入Spring中的;在第三方框架在和Spring整合时,注册BeanDefinition和FactoryBean这些注入方式也会使用的比较多;至于配置文件和注册创建完成的Bean的方式,有但是不多。

最后,本文所有的示例代码地址:

https://github.com/sanyou3/spring-bean-injection.git

往期热门文章推荐

RocketMQ消息短暂而又精彩的一生

写出漂亮代码的45个小技巧

两万字盘点那些被玩烂了的设计模式

RocketMQ保姆级教程

三万字盘点Spring/Boot的那些常用扩展点

@Async注解的坑,小心

扫码或者搜索关注公众号 三友的java日记 ,及时干货不错过,公众号致力于通过画图加上通俗易懂的语言讲解技术,让技术更加容易学习,回复 面试 即可获得一套面试真题。

相关文章
|
25天前
|
Java Spring
在使用Spring的`@Value`注解注入属性值时,有一些特殊字符需要注意
【10月更文挑战第9天】在使用Spring的`@Value`注解注入属性值时,需注意一些特殊字符的正确处理方法,包括空格、引号、反斜杠、新行、制表符、逗号、大括号、$、百分号及其他特殊字符。通过适当包裹或转义,确保这些字符能被正确解析和注入。
|
1月前
|
Java 测试技术 程序员
为什么Spring不推荐@Autowired用于字段注入?
作为Java程序员,Spring框架在日常开发中使用频繁,其依赖注入机制带来了极大的便利。然而,尽管@Autowired注解简化了依赖注入,Spring官方却不推荐在字段上使用它。本文将探讨字段注入的现状及其存在的问题,如难以进行单元测试、违反单一职责原则及易引发NPE等,并介绍为何Spring推荐构造器注入,包括增强代码可读性和维护性、方便单元测试以及避免NPE等问题。通过示例代码展示如何将字段注入重构为构造器注入,提高代码质量。
|
10天前
|
缓存 Java Spring
实战指南:四种调整 Spring Bean 初始化顺序的方案
本文探讨了如何调整 Spring Boot 中 Bean 的初始化顺序,以满足业务需求。文章通过四种方案进行了详细分析: 1. **方案一 (@Order)**:通过 `@Order` 注解设置 Bean 的初始化顺序,但发现 `@PostConstruct` 会影响顺序。 2. **方案二 (SmartInitializingSingleton)**:在所有单例 Bean 初始化后执行额外的初始化工作,但无法精确控制特定 Bean 的顺序。 3. **方案三 (@DependsOn)**:通过 `@DependsOn` 注解指定 Bean 之间的依赖关系,成功实现顺序控制,但耦合性较高。
实战指南:四种调整 Spring Bean 初始化顺序的方案
|
1月前
|
XML Java 数据格式
Spring从入门到入土(bean的一些子标签及注解的使用)
本文详细介绍了Spring框架中Bean的创建和使用,包括使用XML配置文件中的标签和注解来创建和管理Bean,以及如何通过构造器、Setter方法和属性注入来配置Bean。
66 9
Spring从入门到入土(bean的一些子标签及注解的使用)
|
28天前
|
Java 测试技术 Windows
咦!Spring容器里为什么没有我需要的Bean?
【10月更文挑战第11天】项目经理给小菜分配了一个紧急需求,小菜迅速搭建了一个SpringBoot项目并完成了开发。然而,启动测试时发现接口404,原因是控制器包不在默认扫描路径下。通过配置`@ComponentScan`的`basePackages`字段,解决了问题。总结:`@SpringBootApplication`默认只扫描当前包下的组件,需要扫描其他包时需配置`@ComponentScan`。
|
1月前
|
Java 开发者 Spring
Spring bean的生命周期详解!
本文详细解析Spring Bean的生命周期及其核心概念,并深入源码分析。Spring Bean是Spring框架的核心,由容器管理其生命周期。从实例化到销毁,共经历十个阶段,包括属性赋值、接口回调、初始化及销毁等。通过剖析`BeanFactory`、`ApplicationContext`等关键接口与类,帮助你深入了解Spring Bean的管理机制。希望本文能助你更好地掌握Spring Bean生命周期。
72 1
|
1月前
|
Java Spring
获取spring工厂中bean对象的两种方式
获取spring工厂中bean对象的两种方式
37 1
|
1月前
|
缓存 Java Spring
源码解读:Spring如何解决构造器注入的循环依赖?
本文详细探讨了Spring框架中的循环依赖问题,包括构造器注入和字段注入两种情况,并重点分析了构造器注入循环依赖的解决方案。文章通过具体示例展示了循环依赖的错误信息及常见场景,提出了三种解决方法:重构代码、使用字段依赖注入以及使用`@Lazy`注解。其中,`@Lazy`注解通过延迟初始化和动态代理机制有效解决了循环依赖问题。作者建议优先使用`@Lazy`注解,并提供了详细的源码解析和调试截图,帮助读者深入理解其实现机制。
29 1
|
1月前
|
Java 开发者 Spring
Spring bean的生命周期详解!
本文详细介绍了Spring框架中的核心概念——Spring Bean的生命周期,包括实例化、属性赋值、接口回调、初始化、使用及销毁等10个阶段,并深入剖析了相关源码,如`BeanFactory`、`DefaultListableBeanFactory`和`BeanPostProcessor`等关键类与接口。通过理解这些核心组件,读者可以更好地掌握Spring Bean的管理和控制机制。
84 1
|
2月前
|
XML Java 数据格式
spring复习03,注解配置管理bean
Spring框架中使用注解配置管理bean的方法,包括常用注解的标识组件、扫描组件、基于注解的自动装配以及使用注解后的注意事项,并提供了一个基于注解自动装配的完整示例。
spring复习03,注解配置管理bean