Java Spring

xml 方式

最早接触Spring的时候,用的还是SSH框架,不知道大家对这个还有印象吗?所有的bean的注入得依靠xml文件来完成。
它的注入方式分为:set方法注入、构造方法注入、字段注入,而注入类型分为值类型注入(8种基本数据类型)和引用类型注入(将依赖对象注入)。
以下是set方法注入的简单样例

  1. <bean name="teacher" class="org.springframework.demo.model.Teacher">
  2. <property name="name" value="阿Q"></property>
  3. </bean>

对应的实体类代码

  1. public class Teacher {
  2. private String name;
  3. public void setName(String name) {
  4. this.name = name;
  5. }
  6. }

xml方式存在的缺点如下:

  1. xml文件配置起来比较麻烦,既要维护代码又要维护配置文件,开发效率低;
  2. 项目中配置文件过多,维护起来比较困难;
  3. 程序编译期间无法对配置项的正确性进行验证,只能在运行期发现并且出错之后不易排查;
  4. 解析xml时,无论是将xml一次性装进内存,还是一行一行解析,都会占用内存资源,影响性能。

    注解方式

    随着Spring的发展,Spring 2.5开始出现了一系列注解,除了经常使用的@Controller@Service@Repository@Component 之外,还有一些比较常用的方式,接下来简单了解下。

    @Configuration + @Bean

    当需要引入第三方的jar包时,可以用@Bean注解来标注,同时需要搭配@Configuration来使用。
  • @Configuration用来声明一个配置类,可以理解为xml的<beans>标签
  • @Bean 用来声明一个bean,将其加入到Spring容器中,可以理解为xml的<bean>标签

简单样例:将 RedisTemplate 注入 Spring

  1. @Configuration
  2. public class RedisConfig {
  3. @Bean
  4. public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory redisConnectionFactory) {
  5. RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
  6. ......
  7. return redisTemplate;
  8. }
  9. }

@Import

在翻看Spring源码的过程中,经常会看到@Import注解,它也可以用来将第三方jar包注入Spring,但是它只可以作用在上。
例如在注解EnableSpringConfigured上就包含了@Import注解,用于将SpringConfiguredConfiguration配置文件加载进Spring容器。

  1. @Import(SpringConfiguredConfiguration.class)
  2. public @interface EnableSpringConfigured {}

@Import的value值是一个数组,一个一个注入比较繁琐,因此可以搭配ImportSelector接口来使用,用法如下:

  1. @Configuration
  2. @Import(MyImportSelector.class)
  3. public class MyConfig {}
  4. public class MyImportSelector implements ImportSelector {
  5. @Override
  6. public String[] selectImports(AnnotationMetadata annotationMetadata) {
  7. return new String[]{"org.springframework.demo.model.Teacher","org.springframework.demo.model.Student"};
  8. }
  9. }

其中selectImports方法返回的数组就会通过@Import注解注入到Spring容器中。
无独有偶,ImportBeanDefinitionRegistrar接口也提供了注入bean的方法。

  1. @Import(AspectJAutoProxyRegistrar.class)
  2. public @interface EnableAspectJAutoProxy {
  3. ......
  4. }

点击AspectJAutoProxyRegistrar类,发现它实现了ImportBeanDefinitionRegistrar接口,它的registerBeanDefinitions方法便是注入bean的过程,可以参考下。
如果觉得源代码比较难懂,可以看一下自定义的类

  1. @Configuration
  2. @Import(value = {MyImportBeanDefinitionRegistrar.class})
  3. public class MyConfig {}
  4. public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
  5. @Override
  6. public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
  7. BeanDefinitionRegistry registry) {
  8. RootBeanDefinition tDefinition = new RootBeanDefinition(Teacher.class);
  9. // 注册 Bean,并指定bean的名称和类型
  10. registry.registerBeanDefinition("teacher", tDefinition);
  11. }
  12. }
  13. }

这样就把Teacher类注入到Spring容器中了。

FactoryBean

提到FactoryBean,就不得不与BeanFactory比较一番。

  • BeanFactory: 是 Factory, IOC容器或者对象工厂,所有的Bean都由它进行管理
  • FactoryBean: 是Bean ,是一个能产生或者修饰对象生成的工厂 Bean,实现与工厂模式和修饰器模式类似

那么FactoryBean是如何实现bean注入的呢?
先定义实现了FactoryBean接口的类

  1. public class TeacherFactoryBean implements FactoryBean<Teacher> {
  2. /**
  3. * 返回此工厂管理的对象实例
  4. **/
  5. @Override
  6. public Teacher getObject() throws Exception {
  7. return new Teacher();
  8. }
  9. /**
  10. * 返回此 FactoryBean 创建的对象的类型
  11. **/
  12. @Override
  13. public Class<?> getObjectType() {
  14. return Teacher.class;
  15. }
  16. }

然后通过 @Configuration + @Bean的方式将TeacherFactoryBean加入到容器中

  1. @Configuration
  2. public class MyConfig {
  3. @Bean
  4. public TeacherFactoryBean teacherFactoryBean(){
  5. return new TeacherFactoryBean();
  6. }
  7. }

注意:没有向容器中注入Teacher,而是直接注入的TeacherFactoryBean,然后从容器中拿Teacher这个类型的bean,成功运行。

BDRegistryPostProcessor

源码

  1. public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {
  2. // 注册bean到spring容器中
  3. void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;
  4. }
  5. @FunctionalInterface
  6. public interface BeanFactoryPostProcessor {
  7. void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
  8. }

BeanFactoryPostProcessor接口是BeanFactory的后置处理器,方法postProcessBeanFactory对bean的定义进行控制。重点来看看postProcessBeanDefinitionRegistry方法:它的参数是BeanDefinitionRegistry,顾名思义就是与BeanDefinition注册相关的。
Bean 注入 Spring 容器的方式 - 图1
通过观察该类,可以发现它里边包含了registerBeanDefinition方法,这个不就是想要的吗?为了能更好的使用该接口来达到注入bean的目的,先来看看Spring是如何操作此接口的。
Bean 注入 Spring 容器的方式 - 图2
看下invokeBeanFactoryPostProcessors方法,会发现没有实现PriorityOrderedOrdered的bean(这种跟自定义的实现类有关)会执行以下代码。

  1. while (reiterate) {
  2. ......
  3. invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
  4. ......
  5. }

进入该方法

  1. private static void invokeBeanDefinitionRegistryPostProcessors(
  2. Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors,
  3. BeanDefinitionRegistry registry) {
  4. for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) {
  5. postProcessor.postProcessBeanDefinitionRegistry(registry);
  6. }
  7. }

会发现实现了BeanDefinitionRegistryPostProcessor接口的bean,其postProcessBeanDefinitionRegistry方法会被调用,也就是说如果自定义接口实现该接口,它的postProcessBeanDefinitionRegistry方法也会被执行。

实战

话不多说,直接上代码。自定义接口实现类

  1. public class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
  2. /**
  3. * 初始化过程中先执行
  4. **/
  5. @Override
  6. public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
  7. RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(Teacher.class);
  8. //Teacher 的定义注册到spring容器中
  9. registry.registerBeanDefinition("teacher", rootBeanDefinition);
  10. }
  11. /**
  12. * 初始化过程中后执行
  13. **/
  14. @Override
  15. public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {}
  16. }

启动类代码

  1. public static void main(String[] args) {
  2. AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
  3. MyBeanDefinitionRegistryPostProcessor postProcessor = new MyBeanDefinitionRegistryPostProcessor();
  4. //将自定义实现类加入 Spring 容器
  5. context.addBeanFactoryPostProcessor(postProcessor);
  6. context.refresh();
  7. Teacher bean = context.getBean(Teacher.class);
  8. System.out.println(bean);
  9. }

启动并打印结果

  1. org.springframework.demo.model.Teacher@2473d930

发现已经注入到Spring容器中了。以上就是总结的几种将bean注入Spring容器的方式。