第四章 SpringBean基础

SpringBean基础

1.SpringBean定义

在Spring的场景下如何去定义一个Bean?这种Bean跟传统的Java Bean是有些区别的

什么是BeanDefinition?按照字面意思,是Bean的定义。

BeanDefinition是SpringFramework中定义Bean的配置元信息接口,有两个方面的因素,一个它是元配置信息或者说配置元信息,第二个方面它是一个接口。

主要包括:

  • Bean的类名,这个类名必须包括包名即全限定名,对应的类必须是一个实现类
  • 关于Bean的配置元信息,这个元信息包括其行为,作用域,自动绑定的模式,生命周期回调等。自动绑定即Autowiring这种模式。生命周期回调如初始化,销毁的回调。还有其他的一些定义方式。
  • 其他Bean引用即Bean和Bean之间的合作关系,又可称作合作者(Collaborators)或者依赖(Dependencies)。合作者我们有可以认为它是Bean的引用关系。比如说依赖注入,把合作者或者说引用的Bean注入到里面来。依赖注入不只是注入Bean,而且还可以注入其他的配置,比如说配置设置
  • 配置设置,比如Bean配置(Properties),假设这个Bean刚好对应线程池或者数据库连接池,这个Bean会有一些包含的大小和相关的属性。

2.BeanDefinition元信息

在定义SpringBean的过程中会有一些元信息,包括BeanDefinition这些元信息是如何进行呈现的。

属性(Property)说明
ClassBean 全类名,必须是具体类,不能用抽象类或接口,因为接口或者抽象类不能实例化
NameBean的名称或ID,即Bean的识别符
ScopeBean的作用域(如:singleton、prototype等)
Constructor argumentsBean 构造器参数(用于依赖注入),当Bean没有指定的构造器方法的时候,需要我们指定构造函数,构造器注入
PropertiesBean 属性设置(用于依赖注入),setter注入
Autowiring modeBean 自动绑定模式(有三种,通过byName,byType或byConstructor),这种方式是关于如何把属性和外面的引用来进行自动的关联或自动绑定,也即这种方式是可以进行配置的,所以这也称为容器或者Bean的一些配置元信息
Lazy initialization modeBean 延迟初始化模式(延迟和非延迟),默认情况是非延迟,即Bean在容器的启动过程中会实时地进行初始化,这时候它相应的属性或者配置会进行一定的设置。非延迟初始化,在需要的时候才进行初始化,有效减少启动时间
Initialization methodBean 初始化回调方法名称
Destruction methodBean 销毁回调方法名称
  • BeanDefinition配置 - BeanDefinition通过XML配置的方式会多一点

    • 通过BeanDefinitionBuilder
      • genericBeanDefinition
        • 一般性的BeanDefinition表示它是一个非根的或者非最顶层的
      • rootBeanDefinition
        • 最根部的或者最顶层的东西,其没有parent
    • 通过AbstractBeanDefinition及其派生类
    • 代码示例
    import org.springframework.beans.MutablePropertyValues;
    import org.springframework.beans.factory.config.BeanDefinition;
    import org.springframework.beans.factory.support.BeanDefinitionBuilder;
    import org.springframework.beans.factory.support.GenericBeanDefinition;
    
    /**
     * {@link org.springframework.beans.factory.config.BeanDefinition} 构建示例
     * @Description
     * @Author WenZhiLuo
     * @Date 2021-06-08 23:54
     */
    public class BeanDefinitionCreationDemo {
        public static void main(String[] args) throws IllegalAccessException, InstantiationException {
            // 1.通过 BeanDefinitionBuilder 构建,
            BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(User.class);
            beanDefinitionBuilder.addPropertyValue("id", 1);
            beanDefinitionBuilder.addPropertyValue("name", "大大智有梦想");
            BeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
            System.out.println(beanDefinition);
    
            // 2.通过 AbstractBeanDefinition 及其派生类
            GenericBeanDefinition genericBeanDefinition = new GenericBeanDefinition();
            genericBeanDefinition.setBeanClass(User.class);
            // 通过 MutablePropertyValues 批量操作属性
            MutablePropertyValues propertyValues = new MutablePropertyValues();
    //        propertyValues.addPropertyValue("id", 1);
    //        propertyValues.addPropertyValue("name", "小小智也有梦想");
            propertyValues.add("id", 1).add("name", "小小智也有梦");
            genericBeanDefinition.setPropertyValues(propertyValues);
            System.out.println(genericBeanDefinition);
        }
    }
    
    public class User {
        private Integer id;
        private String name;
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        @Override
        public String toString() {
            return "User{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    '}';
        }
    }
    

3.命名SpringBean

基本上每个Bean都会有一个名字,但是名字是不是一定是必须的?

id和name命名Bean,哪个更好?

  • Bean的名称

    • 每个 Bean 拥有一个或多个标识符(identifiers),这些标识符在 Bean 所在的容器必须是唯一的(即所在的BeanDefinition或者说BeanFactory里面是唯一的,并不是说整个应用是唯一的)。通常,一个 Bean 仅有一个标识符(即id或name),如果需要额外的,可考虑使用别名(Alias)来扩充。
    • 在基于 XML 的配置元信息中(基于XML配置不一定是基于XML文件,因为XML只是一个资源,可以存储到文件,也可以存储到网络的一些资源上面),开发人员可用 id 或者 name 属性(这里的属性是XML里面的元素属性即XML标签的属性)来规定 Bean 的 标识符。通常Bean 的 标识符由字母组成,允许出现特殊字符。如果要想引入 Bean 的别名的话,可在name 属性使用半角逗号(“,”)或分号(“;”) 来间隔,即第一个作为名称,后面的作为别名。别用全角,全角是中文。
    • Bean 的 id 或 name 属性并非必须制定,如果留空的话,容器会为 Bean 自动生成一个唯一的名称。Bean 的命名尽管没有限制,不过官方建议采用驼峰的方式,更符合 Java 的命名约定。
  • Bean 名称生成器(BeanNameGenerator)

    • 由 Spring Framework 2.0.3 引入(注意,SpringAPI引用的方式不太符合Java标准,在Java里不允许在小版本里面引入API的,需要注意一下版本的限定。比如Spring2它代表了Spring2.0到2.5之间的版本,不限定于就是说它一定是对Spring2.0.0这么一个版本。),

    • 框架內建两种实现:

      • DefaultBeanNameGenerator:默认通用 BeanNameGenerator 实现

      • AnnotationBeanNameGenerator:基于注解扫描的 BeanNameGenerator 实现,起始于Spring Framework 2.5,关联的官方文档:

        With component scanning in the classpath, Spring generates bean names for unnamed components,

        following the rules described earlier: essentially, taking the simple class name and turning its initial

        character to lower-case. However, in the (unusual) special case when there is more than one character and both the first and second characters are upper case, the original casing gets preserved. These are the same rules as defined by java.beans.Introspector.decapitalize (which Spring uses here)

      • 在注解方式,Bean不命名的方式会多点,XML的方式命名会多点,命名还是不命名哪个比较好没有必要得讨论空间。

    代码示例

    package org.springframework.beans.factory.support;
    
    import org.springframework.beans.factory.config.BeanDefinition;
    
    /**
     * Strategy interface for generating bean names for bean definitions.
     *
     * @author Juergen Hoeller
     * @since 2.0.3
     */
    public interface BeanNameGenerator {
    
    	/**
    	 * Generate a bean name for the given bean definition.
    	 * @param definition the bean definition to generate a name for
    	 * @param registry the bean definition registry that the given definition
    	 * is supposed to be registered with
    	 * @return the generated bean name
    	 */
    	String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry);
    
    }
    

    先看通用实现

    package org.springframework.beans.factory.support;
    
    import org.springframework.beans.factory.config.BeanDefinition;
    
    /**
     * Default implementation of the {@link BeanNameGenerator} interface, delegating to
     * {@link BeanDefinitionReaderUtils#generateBeanName(BeanDefinition, BeanDefinitionRegistry)}.
     *
     * @author Juergen Hoeller
     * @since 2.0.3
     */
    public class DefaultBeanNameGenerator implements BeanNameGenerator {
    
    	/**
    	 * A convenient constant for a default {@code DefaultBeanNameGenerator} instance,
    	 * as used for {@link AbstractBeanDefinitionReader} setup.
    	 * @since 5.2
    	 * 为了节省内存开销,采用单例,单例一般都需要把构造函数设置为private的,避免外部初始化。之所以构造器不设置为private的,因为DefaultBeanNameGenerator是一个公有的API,如果擅自从高版本里把构造函数访问限定设置为非public的,那么其他版本其他老的兼容方式就会出问题,因此就保持原样。Spring希望如果要使用这个类就通过单例来呈现
    	 */
    	public static final DefaultBeanNameGenerator INSTANCE = new DefaultBeanNameGenerator();
    
    
    	@Override
    	public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
    		return BeanDefinitionReaderUtils.generateBeanName(definition, registry);
    	}
    
    }
    
    

    跟进BeanDefinitionReaderUtils.generateBeanName方法

    public abstract class BeanDefinitionReaderUtils {
    /**
    	 * Generate a bean name for the given bean definition, unique within the
    	 * given bean factory.
    	 * @param definition the bean definition to generate a bean name for
    	 * @param registry the bean factory that the definition is going to be
    	 * registered with (to check for existing bean names)
    	 * @param isInnerBean whether the given bean definition will be registered
    	 * as inner bean or as top-level bean (allowing for special name generation
    	 * for inner beans versus top-level beans)
    	 * @return the generated bean name
    	 * @throws BeanDefinitionStoreException if no unique name can be generated
    	 * for the given bean definition
    	 */
    public static String generateBeanName(
    			BeanDefinition definition, BeanDefinitionRegistry registry, boolean isInnerBean)
    			throws BeanDefinitionStoreException {
    
    		String generatedBeanName = definition.getBeanClassName();
    		if (generatedBeanName == null) {
    			if (definition.getParentName() != null) {
    				generatedBeanName = definition.getParentName() + "$child";
    			}
    			else if (definition.getFactoryBeanName() != null) {
    				generatedBeanName = definition.getFactoryBeanName() + "$created";
    			}
    		}
    		if (!StringUtils.hasText(generatedBeanName)) {
    			throw new BeanDefinitionStoreException("Unnamed bean definition specifies neither " +
    					"'class' nor 'parent' nor 'factory-bean' - can't generate bean name");
    		}
    
    		String id = generatedBeanName;
    		if (isInnerBean) {
    			// Inner bean: generate identity hashcode suffix.
    			id = generatedBeanName + GENERATED_BEAN_NAME_SEPARATOR + ObjectUtils.getIdentityHexString(definition);
    		}
    		else {
    			// Top-level bean: use plain class name with unique suffix if necessary.
    			return uniqueBeanName(generatedBeanName, registry);
    		}
    		return id;
    	}
    }
    

    AnnotationBeanNameGenerator的BeanName处理器,与@Component,@Repository,@Service和@Controller都是在Spring2.5出现的,实现的相应细节无需过度关注,只需了解具体API的一个位置和一个基本的使用方略。

4.SpringBean的别名

Bean的别名为什么一定需要?或者说什么场景下需要Bean的别名?

  • Bean 别名(Alias)的价值

    • 复用现有的 BeanDefinition

    • 更具有场景化的命名方法,比如:

      <alias name="myApp-dataSource" alias="subsystemA-dataSource"/>
      <alias name="myApp-dataSource" alias="subsystemB-dataSource"/>
      

    代码示例:

    dependency-lookup-context.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans
            xmlns="http://www.springframework.org/schema/beans"
            xmlns:context="http://www.springframework.org/schema/context"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            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">
    
        <!-- Root BeanDefinition 不需要合并,不存在 parent -->
        <!-- 普通 beanDefinition GenericBeanDefinition -->
        <!-- 经过合并后 GenericBeanDefinition 变成 RootBeanDefinition -->
        <bean id="user" class="org.geekbang.thinking.in.spring.ioc.overview.domain.User">
            <property name="id" value="1"/>
            <property name="name" value="小马哥"/>
        </bean>
    </beans>
    
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
            https://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <!-- 导入第三方 Spring XML 配置文件 -->
        <import resource="classpath:/META-INF/dependency-lookup-context.xml" />
    
        <!-- 将 Spring 容器中 "user" Bean 关联/建立别名 - "xiaomage-user" -->
        <alias name="user" alias="xiaomage-user" />
    </beans>
    
    package org.geekbang.thinking.in.spring.bean.definition;
    
    import org.geekbang.thinking.in.spring.ioc.overview.domain.User;
    import org.springframework.beans.factory.BeanFactory;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    /**
     * Bean 别名示例
     *
     * @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
     * @since
     */
    public class BeanAliasDemo {
    
        public static void main(String[] args) {
            // 配置 XML 配置文件
            // 启动 Spring 应用上下文,加上classpath前缀更具语义
            BeanFactory beanFactory = new ClassPathXmlApplicationContext("classpath:/META-INF/bean-definitions-context.xml");
            // 通过别名 xiaomage-user 获取曾用名 user 的 bean
            User user = beanFactory.getBean("user", User.class);
            User xiaomageUser = beanFactory.getBean("xiaomage-user", User.class);
            System.out.println("xiaomage-user 是否与 user Bean 相同:" + (user == xiaomageUser)); //true
        }
    }
    

5.注册SpringBean

如何注册一个SpringBean?跟前面的BeanDefinition又有什么区别?

  • BeanDefinition 注册 如果用注解的方式就一直用注解,如果用XML就一直用XML,这样通畅一点。在Spring里,不会重复注册。

    • XML 配置元信息

      • <bean name="" …>
    • Java注解配置元信息

      • @Bean
      • @Component
      • @Import
    • JavaAPI配置元信息(最原始的方式)

      • 命名方式:BeanDefinitionRegistry#registerBeanDefinition(String,BeanDefinition)

      • 非命名方式:BeanDefinitionReaderUtils#registerWithGeneratedName(AbstractBeanDefinition,Be

        anDefinitionRegistry)

      • 配置类方式:AnnotatedBeanDefinitionReader#register(Class…)

    import org.geekbang.thinking.in.spring.ioc.overview.domain.User;
    import org.springframework.beans.factory.support.BeanDefinitionBuilder;
    import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
    import org.springframework.beans.factory.support.BeanDefinitionRegistry;
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Import;
    import org.springframework.stereotype.Component;
    import org.springframework.util.StringUtils;
    import java.util.Map;
    import static org.springframework.beans.factory.support.BeanDefinitionBuilder.genericBeanDefinition;
    
    /**
     * 注解 BeanDefinition 示例
     *
     * @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
     * @since
     */
    // 3. 通过 @Import 来进行导入
    @Import(AnnotationBeanDefinitionDemo.Config.class)
    public class AnnotationBeanDefinitionDemo {
    
        public static void main(String[] args) {
            // 创建 BeanFactory 容器
            AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
            // 注册 Configuration Class(配置类)
            applicationContext.register(AnnotationBeanDefinitionDemo.class);
    
            // 通过 BeanDefinition 注册 API 实现
            // 1.命名 Bean 的注册方式
            registerUserBeanDefinition(applicationContext, "mercyblitz-user");
    
            // 2. 非命名 Bean 的注册方法
            registerUserBeanDefinition(applicationContext);
    
            // 启动 Spring 应用上下文
            applicationContext.refresh();
            // 按照类型依赖查找
            System.out.println("Config 类型的所有 Beans" + applicationContext.getBeansOfType(Config.class));
            System.out.println("User 类型的所有 Beans" + applicationContext.getBeansOfType(User.class));
            // 显示地关闭 Spring 应用上下文
            applicationContext.close();
        }
    
        public static void registerUserBeanDefinition(BeanDefinitionRegistry registry, String beanName) {
            BeanDefinitionBuilder beanDefinitionBuilder = genericBeanDefinition(User.class);
            beanDefinitionBuilder
                    .addPropertyValue("id", 1L)
                    .addPropertyValue("name", "小马哥");
    
            // 判断如果 beanName 参数存在时
            if (StringUtils.hasText(beanName)) {
                // 注册 BeanDefinition
                registry.registerBeanDefinition(beanName, beanDefinitionBuilder.getBeanDefinition());
            } else {
                // 非命名 Bean 注册方法
        BeanDefinitionReaderUtils.registerWithGeneratedName(beanDefinitionBuilder.getBeanDefinition(), registry);
            }
        }
    
        public static void registerUserBeanDefinition(BeanDefinitionRegistry registry) {
            registerUserBeanDefinition(registry, null);
        }
    
        // 2. 通过 @Component 方式
        @Component // 定义当前类作为 Spring Bean(组件)
        public static class Config {
    
            // 1. 通过 @Bean 方式定义
    
            /**
             * 通过 Java 注解的方式,定义了一个 Bean
             */
            @Bean(name = {"user", "xiaomage-user"})
            public User user() {
                User user = new User();
                user.setId(1L);
                user.setName("小马哥");
                return user;
            }
        }
    
    
    }
    

6.实例化SpringBean

如何把一个SpringBean从一个BeanDefinition实例化成一个SpringBean?

  • Bean 实例化(Instantiation)

    • 常规方式

      • 通过构造器(配置元信息:XML、Java 注解和 Java API )
      • 通过静态工厂方法(配置元信息:XML 和 Java API )
      • 通过 Bean 工厂方法(配置元信息:XML和 Java API )
      • 通过 FactoryBean(配置元信息:XML、Java 注解和 Java API )

      代码示例:

      package org.geekbang.thinking.in.spring.bean.factory;
      
      import org.geekbang.thinking.in.spring.ioc.overview.domain.User;
      
      /**
       * {@link User} 工厂类
       *
       * @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
       * @since
       */
      public interface UserFactory {
      
          default User createUser() {
              return User.createUser();
          }
      }
      
      
      package org.geekbang.thinking.in.spring.bean.factory;
      
      import org.geekbang.thinking.in.spring.ioc.overview.domain.User;
      import org.springframework.beans.factory.FactoryBean;
      
      /**
       * {@link User} Bean 的 {@link org.springframework.beans.factory.FactoryBean} 实现
       *
       * @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
       * @since
       */
      public class UserFactoryBean implements FactoryBean {
      
          @Override
          public Object getObject() throws Exception {
              return User.createUser();
          }
      
          @Override
          public Class<?> getObjectType() {
              return User.class;
          }
      }
      
      public class DefaultUserFactory implements UserFactory{
      
      }
      

      bean-instantiation-context.xml

      <?xml version="1.0" encoding="UTF-8"?>
      <beans xmlns="http://www.springframework.org/schema/beans"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://www.springframework.org/schema/beans
              https://www.springframework.org/schema/beans/spring-beans.xsd">
      
         <!-- 静态方法实例化 Bean -->
          <bean id="user-by-static-method" class="org.geekbang.thinking.in.spring.ioc.overview.domain.User"
                factory-method="createUser"/>
      
         <!-- 实例(Bean)方法实例化 Bean -->
         <bean id="user-by-instance-method" factory-bean="userFactory" factory-method="createUser"/>
      
         <!-- FactoryBean实例化 Bean -->
         <bean id="user-by-factory-bean" class="org.geekbang.thinking.in.spring.bean.factory.UserFactoryBean" />
      
          <bean id="userFactory" class="org.geekbang.thinking.in.spring.bean.factory.DefaultUserFactory"/>
      </beans>
      
      package org.geekbang.thinking.in.spring.bean.definition;
      
      import org.geekbang.thinking.in.spring.ioc.overview.domain.User;
      import org.springframework.beans.factory.BeanFactory;
      import org.springframework.context.support.ClassPathXmlApplicationContext;
      
      /**
       * Bean 实例化示例
       *
       * @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
       * @since
       */
      public class BeanInstantiationDemo {
      
          public static void main(String[] args) {
              // 配置 XML 配置文件
              // 启动 Spring 应用上下文
              BeanFactory beanFactory = new ClassPathXmlApplicationContext("classpath:/META-INF/bean-instantiation-context.xml");
              User user = beanFactory.getBean("user-by-static-method", User.class);
              User userByInstanceMethod = beanFactory.getBean("user-by-instance-method", User.class);
              User userByFactoryBean = beanFactory.getBean("user-by-factory-bean", User.class);
              System.out.println(user);
              System.out.println(userByInstanceMethod);
              System.out.println(userByFactoryBean);
      
              System.out.println(user == userByInstanceMethod);
              System.out.println(user == userByFactoryBean);
      
          }
      }
      
    • 特殊方式

      • 通过 ServiceLoaderFactoryBean(配置元信息:XML、Java 注解和 Java API )

        • ServiceLoader是Java传统的一个API,比如说Java通过ServiceLoader的方式,通过一个接口和类的方式加载它的一个classpath上面的资源上的文件。

        • ServiceLoader可以加载类中定义的PREFIX前缀即META-INF/services/目录下,在classpath的META-INF/servives下创建一个文件,名称是某接口的全限定名称,文件无后缀。文件中直接写上接口实现类的全限定名。然后使用ServiceLoader相关的api即可实现接口实现类的实例化。指定多个相同的实现类不会重复实例化Bean

        • package org.geekbang.thinking.in.spring.bean.definition;
          
          import org.geekbang.thinking.in.spring.bean.factory.DefaultUserFactory;
          import org.geekbang.thinking.in.spring.bean.factory.UserFactory;
          import org.geekbang.thinking.in.spring.ioc.overview.domain.User;
          import org.springframework.beans.factory.BeanFactory;
          import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
          import org.springframework.context.ApplicationContext;
          import org.springframework.context.support.ClassPathXmlApplicationContext;
          
          import java.util.Iterator;
          import java.util.ServiceLoader;
          
          import static java.util.ServiceLoader.load;
          
          /**
           * 特殊的 Bean 实例化示例
           *
           * @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
           * @since
           */
          public class SpecialBeanInstantiationDemo {
          
              public static void main(String[] args) {
                  // 配置 XML 配置文件
                  // 启动 Spring 应用上下文
                  ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:/META-INF/special-bean-instantiation-context.xml");
                  // 通过 ApplicationContext 获取 AutowireCapableBeanFactory
                  AutowireCapableBeanFactory beanFactory = applicationContext.getAutowireCapableBeanFactory();
          
                  //ServiceLoader 第二种方式,配置xml文件
                  ServiceLoader<UserFactory> serviceLoader = beanFactory.getBean("userFactoryServiceLoader", ServiceLoader.class);
          
                  displayServiceLoader(serviceLoader);
          
          
          //        demoServiceLoader();
          
                  // 创建 UserFactory 对象,通过 AutowireCapableBeanFactory
                  UserFactory userFactory = beanFactory.createBean(DefaultUserFactory.class);
                  System.out.println(userFactory.createUser());
          
              }
          
              public static void demoServiceLoader() {
                  //ServiceLoader 第一种方式,通过静态方法加载,配置对应的services下的文件,指明了接口的实现类
                  ServiceLoader<UserFactory> serviceLoader = load(UserFactory.class, Thread.currentThread().getContextClassLoader());
                  displayServiceLoader(serviceLoader);
              }
          
              private static void displayServiceLoader(ServiceLoader<UserFactory> serviceLoader) {
                  Iterator<UserFactory> iterator = serviceLoader.iterator();
                  while (iterator.hasNext()) {
                      UserFactory userFactory = iterator.next();
                      System.out.println(userFactory.createUser());
                  }
              }
          }
          
      • 通过 AutowireCapableBeanFactory#createBean(java.lang.Class, int, boolean)

        • 注意class参数不能是接口或者抽象类,否则不能初始化
      • 通过 BeanDefinitionRegistry#registerBeanDefinition(String,BeanDefinition)

7.初始化SpringBean

初始化有哪些手段进行初始化?在Spring里有两种初始化的方式,一种是关于延迟初始化,一种方式是关于非延迟初始化。默认采用非延迟初始化的方式。

  • Bean 初始化(Initialization)

    • @PostConstruct 标注方法
    • 实现 InitializingBean 接口的 afterPropertiesSet() 方法
    • 自定义初始化方法
      • XML 配置:<bean init-method=”init” … />
      • Java 注解:@Bean(initMethod=”init”)
      • Java API:AbstractBeanDefinition#setInitMethodName(String)
    • 思考:假设以上三种方式均在同一 Bean 中定义,那么这些方法的执行顺序是怎样?
      • spring bean初始化顺序:PostContruct->afterPropertiesSet->自定义init方法
      • 见名知意
        postconstruct 构造后置
        afterpropertiesset 属性填充后
        initmethod bean初始化(可以认为是功能初始化)
    • API的实现,建议记住AbstractBeanDefinition的实现,因为这个实现从Spring1.0开始就已经存在了。idea可以设置满足指定条件的时候进行拦截。打断点,对着红点右击设置Condition。

    代码示例:

    package org.geekbang.thinking.in.spring.bean.factory;
    
    import org.springframework.beans.factory.InitializingBean;
    
    import javax.annotation.PostConstruct;
    
    /**
     * 默认 {@link UserFactory} 实现
     *
     * @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
     * @since
     */
    public class DefaultUserFactory implements UserFactory, InitializingBean {
    
        // 1. 基于 @PostConstruct 注解
        @PostConstruct
        public void init() {
            System.out.println("@PostConstruct : UserFactory 初始化中...");
        }
    
        public void initUserFactory() {
            System.out.println("自定义初始化方法 initUserFactory() : UserFactory 初始化中...");
        }
    
        @Override
        public void afterPropertiesSet() throws Exception {
            System.out.println("InitializingBean#afterPropertiesSet() : UserFactory 初始化中...");
        }
    }
    
    package org.geekbang.thinking.in.spring.bean.definition;
    
    import org.geekbang.thinking.in.spring.bean.factory.DefaultUserFactory;
    import org.geekbang.thinking.in.spring.bean.factory.UserFactory;
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Lazy;
    
    /**
     * Bean 初始化 Demo
     *
     * @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
     * @since
     */
    @Configuration // Configuration Class
    public class BeanInitializationDemo {
    
        public static void main(String[] args) {
            // 创建 BeanFactory 容器
            AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
            // 注册 Configuration Class(配置类)
            applicationContext.register(BeanInitializationDemo.class);
            // 启动 Spring 应用上下文
            applicationContext.refresh();
            // 非延迟初始化在 Spring 应用上下文启动完成后,被初始化
            System.out.println("Spring 应用上下文已启动...");
            // 依赖查找 UserFactory
            UserFactory userFactory = applicationContext.getBean(UserFactory.class);
            System.out.println(userFactory);
            System.out.println("Spring 应用上下文准备关闭...");
            // 关闭 Spring 应用上下文
            applicationContext.close();
            System.out.println("Spring 应用上下文已关闭...");
        }
    
        @Bean(initMethod = "initUserFactory")
        @Lazy
        public UserFactory userFactory() {
            return new DefaultUserFactory();
        }
    }
    

8.延迟初始化SpringBean

  • Bean 延迟初始化(Lazy Initialization)

    • XML 配置:<bean lazy-init=”true” … />
    • Java 注解:@Lazy(true)
  • 思考:当某个 Bean 定义为延迟初始化,那么,Spring 容器返回的对象与非延迟的对象存在怎样的差异?

    • 主要是周期性不同,非延迟初始化在 Spring 应用上下文启动完成后,,当需要时再被初始化。主要是区别是在应用上下文生命周期前后进行输出。在AbstractApplicationContext#refresh方法中有finishBeanFactoryInitialization(beanFactory);方法来初始化所有的剩余的非延迟初始化的单例。
  • 为什么在循环依赖的时候加上@Lazy注解 就可以解决掉呢? 是因为在初始化的时候大家一起互相注入会冲突.等到用到的时候再去初始化就不会冲突了么?原理是什么 可以解答一下嘛?

    • @Lazy 注册加上之后,它返回的对象实际上是一个代理 ,当依赖注入完成时,实际的 Bean 还没有完成初始化,只有首次调用方法或字段时,才开始~
  • 小马哥,我在测试延迟依赖注入的时候,发现只有在@Bean定义的时候加上@Lazy注解,并且还需要在依赖注入的字段上加上@Lazy,才会触发依赖对象的延迟加载,否则在被依赖对象初始化时就会完成依赖对象的初始化。两个@Lazy注解 缺其一都不行

    • public class Company {
          @Lazy
          @Autowired
          private List<TestUser> employee;
      
          public List<TestUser> getEmployee() {
              return employee;
          }
      
          public void setEmployee(List<TestUser> employee) {
              this.employee = employee;
          }
      }
      
      @Configuration
      public class LazyInstantiateDemo {
      
          public static void main(String[] args) {
              AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
              context.register(LazyInstantiateDemo.class);
              context.refresh();
              Company bean = context.getBean(Company.class);
              System.out.println(bean);
          }
      
          @Lazy
          @Bean
          public TestUser testUser() {
              return new TestUser("jack", 1);
          }
      
          @Bean
          public Company company() {
              Company company = new Company();
              return company;
          }
      }
      
    • 因为 @Bean 没有办法做 CGLIB 字节码提升,所以需要特殊处理

    • cglib不是动态代理么,有点懵 - Spring 会将标记@Configuration注解的类进行CGLIB提升,其中的@Bean并不会被提升,如例子中的LazyInstantiateDemo类被CGLIB提升,但testUser()没被提升。所以在依赖注入时需要打上@Lazy标记,Spring将会注入一个代理对象,在实际使用时触发TestUser的初始化,否则在依赖注入的时候,就会触发TestUser的初始化

9.销毁SpringBean

SpringBean使用完成后需要进行销毁,也许我们可以把他关闭,像数据库连接池或者相关的资源,比如说线程池。

  • Bean 销毁(Destroy)- 销毁方法是在调用ApplicationContext#close方法时触发的。
    • @PreDestroy 标注方法
      • 字面意思是在被销毁之前进行操作。这里的Destroy是指Bean对象被销毁,即Bean对象被回收之前,被JavaGC之后才进行操作。
      • 不知道哪里调用的话,可以通过findUsage查看哪里使用到了@PostDestroy,
      • org.springframework.context.annotation.CommonAnnotationBeanPostProcessor#CommonAnnotationBeanPostProcessor
    • 实现 DisposableBean 接口的 destroy() 方法
      • 从ApplicationContext#close方法一直跟下去会发现回调了bean的destroy方法
      • org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#destroyBean
    • 自定义销毁方法
      • XML 配置:<bean destroy=”destroy” … />
      • Java 注解:@Bean(destroy=”destroy”)
      • Java API:AbstractBeanDefinition#setDestroyMethodName(String)
    • 思考:假设以上三种方式均在同一 Bean 中定义,那么这些方法的执行顺序是怎样?
      • 注解第一,覆盖第二,自定义老幺。
    • 感觉对销毁Bean的顺序会有些疑惑,脑海里先入为主的销毁顺序是自定义>DisPosableBean>@PreDestory,原因是初始化的顺序是构造参数后、设置属性后、自定义,假设在不同的初始化阶段做不同的初始化动作,那么销毁的时候应该和初始化顺序相反,先初始化的最后销毁,如果和初始化顺序相同,那么会不会引发其他的问题,例如资源释放,所以不是太明白这么设计的思路?
      • 实际上,这种设计称之为“就近原则”,所谓的“近”是指离用户应用的举例,通常用户配置优先原则。
    • 想问下为什么有@PreDestroy注解了,spring还要提供Disposable 接口的 destroy() 方法呢?
      • 这是因为 @PreDestroy 需要 JDK 5 支持,并且在 JDK 6 开始成为 JDK 的一部分,所以这是高版本的支持,那么在这之前,Spring 是兼容 JDK 5 之前的版本,故使用接口来实现。

10.垃圾回收SpringBean

官方文档中并没有提及,是小马哥特别补充的。

  • Bean 垃圾回收(GC)
    • 关闭 Spring 容器(应用上下文)
    • 执行 GC - 可选,为了更好的模拟效果才这样做
    • Spring Bean 覆盖的 finalize() 方法被回调
      • finalize()方法不一定会被回调,不一定会被回调不代表Bean不会被回收。
@Configuration // Configuration Class
public class BeanInitializationDemo {

    public static void main(String[] args) {
        // 创建 BeanFactory 容器
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        // 注册 Configuration Class(配置类)
        applicationContext.register(BeanInitializationDemo.class);
        // 启动 Spring 应用上下文
        applicationContext.refresh();
        // 非延迟初始化在 Spring 应用上下文启动完成后,被初始化
        System.out.println("Spring 应用上下文已启动...");
        // 依赖查找 UserFactory
        UserFactory userFactory = applicationContext.getBean(UserFactory.class);
        System.out.println(userFactory);
        System.out.println("Spring 应用上下文准备关闭...");
        // 关闭 Spring 应用上下文
        applicationContext.close();
        System.out.println("Spring 应用上下文已关闭...");
    }

    @Bean(initMethod = "initUserFactory", destroyMethod = "doDestroy")
    @Lazy(value = false)
    public UserFactory userFactory() {
        return new DefaultUserFactory();
    }
}
public class DefaultUserFactory implements UserFactory, InitializingBean, DisposableBean {

    // 1. 基于 @PostConstruct 注解
    @PostConstruct
    public void init() {
        System.out.println("@PostConstruct : UserFactory 初始化中...");
    }

    public void initUserFactory() {
        System.out.println("自定义初始化方法 initUserFactory() : UserFactory 初始化中...");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("InitializingBean#afterPropertiesSet() : UserFactory 初始化中...");
    }

    @PreDestroy
    public void preDestroy() {
        System.out.println("@PreDestroy : UserFactory 销毁中...");
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("DisposableBean#destroy() : UserFactory 销毁中...");
    }

    public void doDestroy() {
        System.out.println("自定义销毁方法 doDestroy() : UserFactory 销毁中...");
    }

    @Override
    public void finalize() throws Throwable {
        System.out.println("当前 DefaultUserFactory 对象正在被垃圾回收...");
    }
}

public class BeanGarbageCollectionDemo {

    public static void main(String[] args) throws InterruptedException {
        // 创建 BeanFactory 容器
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        // 注册 Configuration Class(配置类)
        applicationContext.register(BeanInitializationDemo.class);
        // 启动 Spring 应用上下文
        applicationContext.refresh();
        // 关闭 Spring 应用上下文
        applicationContext.close();
        Thread.sleep(5000L);
        // 强制触发 GC
        System.gc();
        Thread.sleep(5000L);
    }
}

11.面试题

1.如何注册一个SpringBean?

通过BeanDefinition和外部单体对象来注册。

1.BeanDefinition的注册通过BeanDefinitionRegistry#registerBeanDefinition方法进行API的调用。

2.外部单体对象是指这个对象的生命周期并不由Spring容器进行管理,但是也可以被容器托管。

public class SingletonBeanRegistrationDemo {

    public static void main(String[] args) throws InterruptedException {
        // 创建 BeanFactory 容器
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        // 创建一个外部 UserFactory 对象
        UserFactory userFactory = new DefaultUserFactory();
        SingletonBeanRegistry singletonBeanRegistry = applicationContext.getBeanFactory();
        // 注册外部单例对象
        singletonBeanRegistry.registerSingleton("userFactory", userFactory);
        // 启动 Spring 应用上下文
        applicationContext.refresh();

        // 通过依赖查找的方式来获取 UserFactory
        UserFactory userFactoryByLookup = applicationContext.getBean("userFactory", UserFactory.class);
        System.out.println("userFactory  == userFactoryByLookup : " + (userFactory == userFactoryByLookup)); //true

        // 关闭 Spring 应用上下文
        applicationContext.close();
    }

}
2.什么是SpringBeanDefinition?
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Spring基础配置主要包括以下几个方面的内容:\[1\]\[2\]\[3\] 1. Spring的类包必须已经放在Spring的类容器下面。这意味着我们需要将Spring的类包放在项目的类路径下,以便Spring容器能够正确加载和管理这些类。 2. 应用程序应当为Spring提供完备的Bean的配置信息。这些配置信息可以通过XML文件或者注解的方式进行定义,用于描述Bean的属性、依赖关系、行为配置等。 3. Bean的类都已经放在Spring的类容器下面。这意味着我们需要将所有需要由Spring管理的Bean的类放在Spring容器能够扫描到的位置,以便Spring能够正确实例化和管理这些Bean。 4. Spring的配置文件是Spring容器对Bean进行生产以及关系注入的图纸。这个配置文件是一个或多个标准的XML文档,其最常见的是ApplicationContext.xml,它是Spring的默认配置文件。在容器启动时,如果找不到其他的配置文件,Spring会尝试加载这个默认的配置文件。 5. Bean的配置信息由Bean的元数据信息组成,包括Bean的实现类、属性信息、依赖关系、行为配置以及创建方式定义等。这些信息用于告诉Spring容器如何实例化和装配Bean,以及如何为上层应用提供准备就绪的运行环境。 综上所述,Spring基础配置包括将Spring的类包放在类路径下、提供完备的Bean的配置信息、将Bean的类放在Spring容器能够扫描到的位置、配置Spring的配置文件以及定义Bean的元数据信息。这些配置将帮助Spring容器正确加载和管理Bean,为应用程序提供准备就绪的运行环境。 #### 引用[.reference_title] - *1* *2* *3* [Spring bean配置的六种方式](https://blog.csdn.net/echizao1839/article/details/88063013)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值