[ Spring核心 ] IOC技术 上篇 吃透 Spring

2年前 (2022) 程序员胖胖胖虎阿
369 0 0

👈 点个关注吧

💋     如果对你有帮助,给博主一个免费的点赞  💋
👋     博客主页🎉   秋日的晚霞
⭐️     更多文章     请关注主页    📝
👋     一起走上java学习之路!
🎄     欢迎各位🔎点赞👍评论收藏⭐️     🎄

文章目录

    • IOC
      • IOC是什么
      • IOC容器创建的方式
      • Bean定义信息对象 BeanDefinition
      • Bean实例化
        • 使用构造函数实例化一个Bean
        • 使用静态工厂实例化一个Bean
        • 使用实例工厂方法实例化一个Bean
      • 依赖注入
        • 基于构造函数的依赖注入
        • 基于 Setter 的依赖注入
        • 选择 构造函数 or Setter
          • 循环依赖问题
      • 基于XML 配置依赖注入
        • 基本数据类型与字符串
        • 对其他Bean的引用
        • 内部Bean
        • 集合
        • 集合合并
        • 空字符串 和 NULL
          • 设置空字符串
          • 设置 NULL 值
      • 惰性初始化Bean
      • Bean的作用范围
        • 单例Bean
        • 原型Bean
    • 自定义Bean性质
      • 生命周期回调
        • 初始化回调
        • 销毁回调
        • 多个初始化方法和销毁方法
          • 多个初始化方法执行顺序
          • 多个销毁方法执行顺序
      • 应用上下文Aware与BeanNaneAware
      • 其他 接口 Aware
      • IOC 容器扩展点
        • `BeanPostProcessor`
        • `BeanFactoryPostProcessor`
      • FactoryBean
    • 基于注解配置Bean
      • @Autowired
        • 用于构造函数
        • 用于传统的Setter方法
        • 用于字段上
      • @Primary
      • @Qualifier 注解
      • `@Resource`
      • `@Value`
      • `@PostConstruct `与 `@PreDestroy`

IOC

IOC是什么

IOC ( Inversion of Control ) 是Spring的控制反转 容器 也被称为 DI (依赖注入) ,负责实例化、配置和组装 Bean。容器通过读取配置元数据来获取有关要实例化、配置和组装哪些对象的说明

控制反转 : 控制了 Bean的生命周期 反转了 对象的依赖获取的方式

IOC容器管理着一个或者多个Bean 这些Bean是使用开发者提供给容器的元数据创建的 . 在容器中 Bean的定义信息用一个对象来描述 这个对象 就是 BeanDefinition

IOC容器创建的方式

常使用的有两种 基于类路径下的spring配置文件和基于注解开发的spring核心配置类

  1. ClassPathXmlApplicationContext
  2. AnnotationConfigApplicationContext

在web环境下,通常不需要使用者显性的去创建Spring容器,springmvc通过监听器监听servlet容器,当容器启动时,创建spring容器

  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>spring.xml</param-value>
  </context-param>
  
  <servlet>
    <servlet-name>springmvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet
		</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>/WEB-INF/springmvc.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  
  <servlet-mapping>
    <servlet-name>springmvc</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener
		</listener-class>
  </listener>

显性创建容器的例子如下

ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.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">

    <!-- services -->

    <bean id="petStore" class="org.springframework.samples.jpetstore.services.PetStoreServiceImpl">
        <property name="accountDao" ref="accountDao"/>
        <property name="itemDao" ref="itemDao"/>
        <!-- additional collaborators and configuration for this bean go here -->
    </bean>

    <!-- more bean definitions for services go here -->

</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">

    <bean id="accountDao"
        class="org.springframework.samples.jpetstore.dao.jpa.JpaAccountDao">
        <!-- additional collaborators and configuration for this bean go here -->
    </bean>

    <bean id="itemDao" class="org.springframework.samples.jpetstore.dao.jpa.JpaItemDao">
        <!-- additional collaborators and configuration for this bean go here -->
    </bean>

    <!-- more bean definitions for data access objects go here -->

</beans>

我们可以通过容器对象获取一个bean

// create and configure beans
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");

// retrieve configured instance
PetStoreService service = context.getBean("petStore", PetStoreService.class);

// use configured instance
List<String> userList = service.getUsernameList();

Bean定义信息对象 BeanDefinition

BeanDefinition 的属性

	/**
	 * Constant for the default scope name: {@code ""}, equivalent to singleton
	 * status unless overridden from a parent bean definition (if applicable).
	 * Bean默认的scope
	 */
	public static final String SCOPE_DEFAULT = "";

	/**
	 * Constant that indicates no external autowiring at all.
	 * @see #setAutowireMode
	 * Bean的注入模式:默认的Bean注入模式,使用这种注入模式时,在依赖其他Bean时,需要使用注解@Autowired进行Bean的注入
	 */
	public static final int AUTOWIRE_NO = AutowireCapableBeanFactory.AUTOWIRE_NO;

	/**
	 * Constant that indicates autowiring bean properties by name.
	 * @see #setAutowireMode
	 * Bean的注入模式:按照Bean的名称进行注入
	 */
	public static final int AUTOWIRE_BY_NAME = AutowireCapableBeanFactory.AUTOWIRE_BY_NAME;

	/**
	 * Constant that indicates autowiring bean properties by type.
	 * @see #setAutowireMode
	 * Bean的注入模式:按照Bean的类型进行注入
	 */
	public static final int AUTOWIRE_BY_TYPE = AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE;

	/**
	 * Constant that indicates autowiring a constructor.
	 * @see #setAutowireMode
	 * Bean的注入模式:按照构造函数进行注入
	 */
	public static final int AUTOWIRE_CONSTRUCTOR = AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR;

	/**
	 * Constant that indicates determining an appropriate autowire strategy
	 * through introspection of the bean class.
	 * @see #setAutowireMode
	 * @deprecated as of Spring 3.0: If you are using mixed autowiring strategies,
	 * use annotation-based autowiring for clearer demarcation of autowiring needs.
	 */
	@Deprecated
	public static final int AUTOWIRE_AUTODETECT = AutowireCapableBeanFactory.AUTOWIRE_AUTODETECT;

	/**
	 * Spring在进行自动装配时,使用的依赖检查模式。分为四种
	 * 自动装配时不进行依赖检查
	 */
	public static final int DEPENDENCY_CHECK_NONE = 0;

	/**
	 * 自动装配时只对依赖的对象类型做检查
	 */
	public static final int DEPENDENCY_CHECK_OBJECTS = 1;

	/**
	 * 自动装配时只对原始的简单类型做检查(包括:集合类,String,基本类型)
	 */
	public static final int DEPENDENCY_CHECK_SIMPLE = 2;

	/**
	 * 自动装配时对所有的类型都做检查,包括: 对象类型和简单类型.
	 */
	public static final int DEPENDENCY_CHECK_ALL = 3;

	/**
	 * Constant that indicates the container should attempt to infer the
	 * {@link #setDestroyMethodName destroy method name} for a bean as opposed to
	 * explicit specification of a method name. The value {@value} is specifically
	 * designed to include characters otherwise illegal in a method name, ensuring
	 * no possibility of collisions with legitimately named methods having the same
	 * name.
	 * <p>Currently, the method names detected during destroy method inference
	 * are "close" and "shutdown", if present on the specific bean class.
	 */
	public static final String INFER_METHOD = "(inferred)";


	/**
	 * Bean对应的class路径
	 *
	 * 使用volatile保证了beanClass的可见性和有序性。
	 */
	@Nullable
	private volatile Object beanClass;

	/**
	 * bean对应的作用域
	 * prototype
	 * singleton
	 * request
	 * session
	 */
	@Nullable
	private String scope = SCOPE_DEFAULT;

	/**
	 * 类是否为抽象类
	 */
	private boolean abstractFlag = false;

	/**
	 * 是否懒加载
	 */
	private boolean lazyInit = false;

	/**
	 * 自动注入模型0. 0表示不支持外部注入
	 */
	private int autowireMode = AUTOWIRE_NO;

	/**
	 * 默认的依赖检查模式,默认是不检查依赖。
	 */
	private int dependencyCheck = DEPENDENCY_CHECK_NONE;

	/**
	 * 用来控制Bean的初始化顺序。
	 * 比如Bean A依赖Bean B,而Bean B中需要初始化一些缓存,初始化缓存的时候又使用了单例模式,没有注入到Spring容器中。而A中需要使用该缓存,这个时候需要指定B应该先被初始化。
	 * 可以再Bean A中声明该属性,表示依赖的Bean,这样Spring在初始化A的时候,会先去初始化B。防止因为B未初始化而导致使用出现未知问题
	 */
	@Nullable
	private String[] dependsOn;

	/**
	 * 该变量表示Spring在依赖注入时,默认注入所有声明的Bean
	 *
	 * 有一种情况,需要设置某些Bean不参与注入:
	 *    当某一个接口有多个实现类时,这些实现类中如果有某些bean不想参与自动注入,可以通过在bean的定义中声明该属性的值为false,表示不参与自动注入.
	 */
	private boolean autowireCandidate = true;

	/**
	 * 当发现多个相同的Bean,比如一个接口有多个实现类,多个实现类对应的Bean就属于重复的Bean,这个时候直接使用会抛出bean重复定义异常。
	 * 需要在某一个Bean上面指定为primary,表示如果发现多个重复的Bean,会优先加载.
	 */
	private boolean primary = false;

	/**
	 * 用来保存Qualifiers,对应Bean的属性Qualifier
	 * 此处这个字段目前【永远】不会被赋值(除非我们手动调用对应方法为其赋值)
	 *
	 * 注意:该属性的值并不是使用 @Qualifier("a") 注解进行的赋值.
	 */
	private final Map<String, AutowireCandidateQualifier> qualifiers = new LinkedHashMap<>();

	/**
	 * 通过这个函数的逻辑初始化Bean
	 * 而不是构造函数或是工厂方法(相当于自己去实例化,而不是交给Bean工厂)
	 */
	@Nullable
	private Supplier<?> instanceSupplier;

	/**
	 * 允许访问非public的构造器和方法
	 */
	private boolean nonPublicAccessAllowed = true;

	/**
	 * 调用Bean的构造方法时,是否使用宽松匹配模式
	 */
	private boolean lenientConstructorResolution = true;

	/**
	 * 工厂Bean的名称,工厂Bean即用来创建Bean的工厂对应的Bean
	 */
	@Nullable
	private String factoryBeanName;

	/**
	 * 工厂方法的名称
	 */
	@Nullable
	private String factoryMethodName;

	/**
	 * 用来控制Spring在通过构造参数实例化对象时,调用哪个构造函数。会根据构造器的参数类型去适配合适的构造函数.
	 */
	@Nullable
	private ConstructorArgumentValues constructorArgumentValues;

	/**
	 * 表示的是可变的属性值。key-value类型,表示这些属性值在被赋值给Bean之前是可以修改的.
	 */
	@Nullable
	private MutablePropertyValues propertyValues;

	/**
	 * 记录哪些方法被覆写了
	 */
	private MethodOverrides methodOverrides = new MethodOverrides();

	/**
	 * 初始化方法的名称 init-method指定的方法名称
	 */
	@Nullable
	private String initMethodName;

	/**
	 * 销毁Bean方法的名称,destroy-method指定的方法名称
	 */
	@Nullable
	private String destroyMethodName;
	/**
	 * 是否执行init-method方法
	 */
	private boolean enforceInitMethod = true;
	/**
	 * 是否执行destroy-method方法
	 */
	private boolean enforceDestroyMethod = true;

	/**
	 * 是否是合成类(是不是应用自定义的,例如生成AOP代理时,会用到某些辅助类,这些辅助类不是应用自定义的,这个就是合成类)
	 * 创建AOP时候为true
	 */
	private boolean synthetic = false;

	/**
	 * Bean的角色,初始化时为ROLE_APPLICATION,表示默认为应用bean
	 * Spring中还存在着一些比较特殊的Bean,比如:基础设施Bean、支持Bean
	 */
	private int role = BeanDefinition.ROLE_APPLICATION;

	/**
	 * Bean定义的描述信息。可以写一些中文描述
	 */
	@Nullable
	private String description;

	/**
	 * Bean定义的资源.
	 */
	@Nullable
	private Resource resource;

BeanDefinition 只是一个Bean创建时的 设计图纸 具体实例化 还是需要容器来做 IOC 容器实例化一个Bean对象有三种方式

Bean实例化

使用构造函数实例化一个Bean

在java中每个类都有一个默认的无参构造函数 (开发者未自己声明一个构造函数时 ) 所以通常你只需要提供 类的全限定类名

列如一下例子基于 XML 实例化一个 Bean

<bean id="exampleBean" class="examples.ExampleBean"/>

<bean name="anotherExample" class="examples.ExampleBeanTwo"/>

如果是一个带参的构造方法 那么你需要向 IOC容器 提供 需要的 材料

使用静态工厂实例化一个Bean

例如定义一个类

public class ClientService {
    private static ClientService clientService = new ClientService();
    private ClientService() {}

    public static ClientService createInstance() {
        return clientService;
    }
}

使用静态工厂实例化一个Bean时,你需要指定 工厂方法所在的类 以及工厂方法名

例如

<bean id="clientService"
    class="examples.ClientService"
    factory-method="createInstance"/>

使用实例工厂方法实例化一个Bean

实例工厂方法与静态工厂方法类似,不同的是你需要先实例化工厂类 在指定工厂Bean的BeanName与工厂方法

如下定义个工厂类

public class ClientService {
    private static ClientService clientService = new ClientService();
    private ClientService() {}

    public static ClientService createInstance() {
        return clientService;
    }
}

配置xml

<bean id="serviceLocator" class="examples.DefaultServiceLocator">

</bean>


<bean id="clientService"
    factory-bean="serviceLocator"
    factory-method="createClientServiceInstance"/>

一个工厂类可以包含多个工厂方法

<bean id="serviceLocator" class="examples.DefaultServiceLocator">
    <!-- inject any dependencies required by this locator bean -->
</bean>

<bean id="clientService"
    factory-bean="serviceLocator"
    factory-method="createClientServiceInstance"/>

<bean id="accountService"
    factory-bean="serviceLocator"
    factory-method="createAccountServiceInstance"/>

依赖注入

基于构造函数的依赖注入

例如

public class SimpleMovieLister {

    // the SimpleMovieLister has a dependency on a MovieFinder
    private final MovieFinder movieFinder;

    // a constructor so that the Spring container can inject a MovieFinder
    public SimpleMovieLister(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // business logic that actually uses the injected MovieFinder is omitted...
}

上述类 只有一个 带参的构造方法 因此实例化需要提供 一个 MovieFinder

如果构造方法需要的参数不存在潜在的多义性 无需显式指定参数的类型 但是如果有多意性 则需要 指定类型 或者 使用索引 甚至是 参数名

如下面的例子

package examples;

public class ExampleBean {

    // Number of years to calculate the Ultimate Answer
    private final int years;

    // The Answer to Life, the Universe, and Everything
    private final String ultimateAnswer;

    public ExampleBean(int years, String ultimateAnswer) {
        this.years = years;
        this.ultimateAnswer = ultimateAnswer;
    }
}

上述 ExampleBean 类实例化需要 两个参数 一个 为 int类型的 years 一个是 String 类型的 ultimateAnswer

如果是指定类型 你需要这样配置

<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg type="int" value="7500000"/>
    <constructor-arg type="java.lang.String" value="42"/>
</bean>

如果使用索引 也可以这样

<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg index="0" value="7500000"/>
    <constructor-arg index="1" value="42"/>
</bean>

最后是使用参数名

<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg name="years" value="7500000"/>
    <constructor-arg name="ultimateAnswer" value="42"/>
</bean>

但使用参数名你必须先使用 @ConstructorProperties显式命名构造函数参数。

package examples;

public class ExampleBean {

    // Fields omitted

    @ConstructorProperties({"years", "ultimateAnswer"})
    public ExampleBean(int years, String ultimateAnswer) {
        this.years = years;
        this.ultimateAnswer = ultimateAnswer;
    }
}

基于 Setter 的依赖注入

基于Setter的依赖注入是指在调用无参构造函数或者工厂方法实例化一个Bean后在Bean上调用Setter完成依赖注入

例如定义一个如下的SimpleMovieLister类

注意 setMovieFinder 不是无参构造方法

public class SimpleMovieLister {

    // the SimpleMovieLister has a dependency on the MovieFinder
    private MovieFinder movieFinder;

    // a setter method so that the Spring container can inject a MovieFinder
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // business logic that actually uses the injected MovieFinder is omitted...
}

选择 构造函数 or Setter

[ Spring核心 ] IOC技术 上篇 吃透 Spring

注意一点 : 构造函数循环依赖 Spring 无法解决

循环依赖问题
类 A 需要通过构造函数注入的类 B 的实例,而类 B 需要通过构造函数注入的类 A 的实例。如果将类 A 和类 B 的 Bean 配置为要相互注入,则 Spring IoC 容器会在运行时检测到此循环引用,并抛出 .BeanCurrentlyInCreationException

如果 A 实例化 B ,Spring IOC容器将在调用Setter 方法之前 完成实例化 B

基于XML 配置依赖注入

基本数据类型与字符串

<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <!-- results in a setDriverClassName(String) call -->
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
    <property name="username" value="root"/>
    <property name="password" value="misterkaoli"/>
</bean>

或者使用P 命名空间

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource"
        destroy-method="close"
        p:driverClassName="com.mysql.jdbc.Driver"
        p:url="jdbc:mysql://localhost:3306/mydb"
        p:username="root"
        p:password="misterkaoli"/>

</beans>

对其他Bean的引用

<!-- in the parent context -->
<bean id="accountService" class="com.something.SimpleAccountService">
    <!-- insert dependencies as required here -->
</bean>
<!-- in the child (descendant) context -->
<bean id="accountService" <!-- bean name is the same as the parent bean -->
    class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="target">
        <ref parent="accountService"/> <!-- notice how we refer to the parent bean -->
    </property>
    <!-- insert other configuration and dependencies as required here -->
</bean>

内部Bean

<bean id="outer" class="...">
    <!-- instead of using a reference to a target bean, simply define the target bean inline -->
    <property name="target">
        <bean class="com.example.Person"> <!-- this is the inner bean -->
            <property name="name" value="Fiona Apple"/>
            <property name="age" value="25"/>
        </bean>
    </property>
</bean>

内置Bean不需要定义ID 或者 名称 及时自动也不会生效 因为 内部Bean 是匿名的 容器在创建BeanDifition时会忽略该标志 因为内部Bean始终是匿名的,并且始终是依赖于外部Bean创建的 不能独立创建

集合

<bean id="moreComplexObject" class="example.ComplexObject">
    <!-- results in a setAdminEmails(java.util.Properties) call -->
    
    <property name="adminEmails">
        <props>
            <prop key="administrator">administrator@example.org</prop>
            <prop key="support">support@example.org</prop>
            <prop key="development">development@example.org</prop>
        </props>
    </property>
    
    <!-- results in a setSomeList(java.util.List) call -->
    <property name="someList">
        <list>
            <value>a list element followed by a reference</value>
            <ref bean="myDataSource" />
        </list>
    </property>
    
    <!-- results in a setSomeMap(java.util.Map) call -->
    <property name="someMap">
        <map>
            <entry key="an entry" value="just some string"/>
            <entry key="a ref" value-ref="myDataSource"/>
        </map>
    </property>
    
    <!-- results in a setSomeSet(java.util.Set) call -->
    <property name="someSet">
        <set>
            <value>just some string</value>
            <ref bean="myDataSource" />
        </set>
    </property>
</bean>

集合合并

Spring容器支持 合并集合 让 子类 继承 父类的的属性 如果设置同一个值 子类将覆盖父类设置的值

子集合的值是合并父集合和子集合的元素的结果,子集合元素将覆盖父集合中指定的值

<beans>
    <bean id="parent" abstract="true" class="example.ComplexObject">
        <property name="adminEmails">
            <props>
                <prop key="administrator">administrator@example.com</prop>
                <prop key="support">support@example.com</prop>
            </props>
        </property>
    </bean>
    <bean id="child" parent="parent">
        <property name="adminEmails">
            <!-- the merge is specified on the child collection definition -->
            <props merge="true">
                <prop key="sales">sales@example.com</prop>
                <prop key="support">support@example.co.uk</prop>
            </props>
        </property>
    </bean>
<beans>

上述例子中 子类bean child 的属性将为

administrator=administrator@example.com
sales=sales@example.com
support=support@example.co.uk

空字符串 和 NULL

设置空字符串
<bean class="ExampleBean">
    <property name="email" value=""/>
</bean>

等效于 JAVA 代码

exampleBean.setEmail("");
设置 NULL 值
<bean class="ExampleBean">
    <property name="email">
        <null/>
    </property>
</bean>

等效于JAVA代码

exampleBean.setEmail(null);

惰性初始化Bean

在xml中 可以设置 lazy-init 的属性 如下

<bean id="lazy" class="com.something.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.something.AnotherBean"/>

当lazy-init 为true时 仅在被需要时初始化

注意: 如果一个懒加载的 Bean 被其他 非懒加载的Bean 依赖 则 在非懒加载的Bean 初始化时 会先完成初始化

也就是说 懒加载将失效

Bean的作用范围

Scope Description
singleton 默认值 将单个 Bean 定义限定为每个 Spring IoC 容器的单个对象实例。
prototype 将单个 Bean 定义的作用域限定为任意数量的对象实例。
request 将单个 Bean 定义的作用域限定为单个 HTTP 请求的生命周期。也就是说,每个 HTTP 请求都有自己的 Bean 实例
session 将单个 Bean 定义限定为 HTTP 的生命周期
application 将单个 Bean 定义的作用域限定为 application 的生命周期
websocket 将单个 Bean 定义的作用域限定为 websocket 的生命周期

单例Bean

Spring 自对 单例Bean的生命周期进行管理 对于单例Bean 当需要获取的bean 的ID 与单例Bean定义的ID一致时 无论获取多少次 都是同一个Bean对象

也就是说 单例Bean初始化后 会放在 单例缓存池中 SingletonObjects ( ConcurrentHashMap ) 获取的对象都是缓存中的Bean对象

原型Bean

对于原型Bean来说 Spring不会对其进行管理 仅仅负责创建 与 单例Bean不同的是不会对其进行缓存

每次向容器中获取一个原型Bean时,它都是一个刚刚创建的全新Bean对象

自定义Bean性质

生命周期回调

初始化回调

接口

org.springframework.beans.factory.InitializingBean

需要重写的方法

void afterPropertiesSet() throws Exception;

该方法将在Bean的初始化方法执行前执行

销毁回调

接口

org.springframework.beans.factory.DisposableBean

需要重写的方法

void destroy() throws Exception;

该方法将在Bean销毁的时候被调用

多个初始化方法和销毁方法

多个初始化方法执行顺序
  1. 注释的方法@PostConstruct
  2. afterPropertiesSet()回调接口定义的InitializingBean
  3. 自定义配置的方法init()
多个销毁方法执行顺序
  1. 注释的方法@PreDestroy
  2. destroy()回调接口定义的DisposableBean
  3. 自定义配置的方法destroy()

应用上下文Aware与BeanNaneAware

public interface ApplicationContextAware {

    void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}

获取应用上下文对象,来获取其他的bean 常用来做Spring容器工具类

public interface BeanNameAware {

    void setBeanName(String name) throws BeansException;
}

在属性填充后,初始化执行前执行 能获取实现了该接口的Bean在容器中的name

其他 接口 Aware

名字 注入的依赖项
ApplicationContextAware 获取ApplicationContext
ApplicationEventPublisherAware 所附事件的事件发布者 。ApplicationContext
BeanClassLoaderAware 用于装入 Bean 类的类装载器。
BeanFactoryAware 获取BeanFactory
BeanNameAware 获取Bean的名称。
LoadTimeWeaverAware 定义了用于在加载时处理类定义的编织器。
MessageSourceAware 配置了解析消息的策略(支持参数化和国际化)。
NotificationPublisherAware spring JMX 通知发布者。
ResourceLoaderAware 配置了加载程序以对资源进行低级访问。
ServletConfigAware 当前容器在其中运行。仅在网络感知的spring 有效。ServletConfig``ApplicationContext
ServletContextAware 当前容器在其中运行。仅在网络感知的spring 有效。ServletContext``ApplicationContext

IOC 容器扩展点

BeanPostProcessor

Bean后置处理器

该扩展点有两个抽象方法

public Object postProcessBeforeInitialization(Object bean, String beanName) 
public Object postProcessAfterInitialization(Object bean, String beanName)

postProcessBeforeInitialization 在初始化方法执行前生效

postProcessAfterInitialization 在初始化方法执行后生效

注意 : 当实现了该接口的类注册为后置处理器后 对容器中的所有Bean生效

后置处理器可以对Bean实例进行任何操作 Spring的AOP就是通过后置处理器实现的 提供代理包装逻辑

BeanFactoryPostProcessor

BeanFactory后置处理器

该扩展点有一个抽象方法

public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)

可以拿到Bean工厂对象 然后通过BeanName 拿到 BeanDefinition对象 对其元数据进行修改

例如类名替换 PropertySourcesPlaceholderConfigurer 和属性重写 PropertyOverrideConfigurer

两者本质上都是 BeanFactoryPostProcessor

BeanFactoryPostProcessor 的 执行时机 是 创建 beanFactory 工厂 ,生成BeanDefition 对象 之后 创建Bean之前

而创建bean又是通过 BeanDefition 所以我们可以通过 BeanFactoryPostProcessor 扩展点 实现对 Bean属性的修改

FactoryBean

如果你需要往容器中添加一个特别复杂的bean对象 适合用JAVA去创建这个对象 你可以通过实现这个接口,重写 getObject() 返回这个对象

FactoryBean 有三个抽象方法

  • T getObject():返回此工厂创建的对象的实例。实例可以共享,具体取决于此工厂返回的是单例还是原型。
  • boolean isSingleton():是否返回单例 默认为 true 也就是返回单例bean 如果是false则为原型
  • Class<?> getObjectType():返回方法返回的对象类型,

基于注解配置Bean

@Autowired

按类型自动装配Bean 如果有两个 按名字找 如果名字不能确定唯一 抛出异常

​ 如果一个没有 也将抛出异常

用于构造函数

public class MovieRecommender {

    private final CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
}

注意 : Spring 从 4.3开始 如果目标Bean只有一个构造函数 则无需对构造函数进行注释 但是如果有多个构造函数,且没有指定主或者默认的构造函数,则必须使用 @Autowired 注释其中一个构造函数,以便告知容器使用哪一个构造函数创建bean

用于传统的Setter方法

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Autowired
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}

用于字段上

public class MovieRecommender {

    private final CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    private MovieCatalog movieCatalog;

    @Autowired
    public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
}

注意: 如果 @Autowired 在容器中找不到对应的属性Bean 将抛出异常 你可以设置 required = false 来避免这个问题

例如下面的例子 在容器中找不到需要的对象时将不填充属性,保持它的默认值 不会抛出异常

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Autowired(required = false)
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}

@Primary

当使用 @Autowired 注解自动装配Bean 而容器中有两个同类型的bean 且名字都不匹配时, 将抛出异常

而使用@Primary 能很好的解决这个问题 被 @Primary 注释的Bean优先级更高

例如

@Configuration
public class MovieConfiguration {

    @Bean
    @Primary
    public MovieCatalog firstMovieCatalog() { ... }

    @Bean
    public MovieCatalog secondMovieCatalog() { ... }

    // ...
}
public class MovieRecommender {

    @Autowired
    private MovieCatalog movieCatalog;

    // ...
}

此时将自动装配 firstMovieCatalog() 方法中返回的Bean 因为使用了 @Primary 注解 优先选择此 Bean

@Qualifier 注解

同样是容器中有两个相同类型的bean对象的场景 且不能通过名字确定唯一的时候 除了@Primary注解外

还可以使用 @Qualifier 注解 指定要自动装配的容器中beanName匹配的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:annotation-config/>

    <bean class="example.SimpleMovieCatalog">
        <qualifier value="main"/> 

        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <qualifier value="action"/> 

        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean id="movieRecommender" class="example.MovieRecommender"/>

</beans>

配置了两个相同类型的bean 但 一个名字为 main 一个名字为 action

使用@@Qualifier() 就能解决自动装配时容器中有两个同类型的bean且名字不能确定唯一的问题

public class MovieRecommender {

    private MovieCatalog movieCatalog;

    private CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    public void prepare(@Qualifier("main") MovieCatalog movieCatalog,
            CustomerPreferenceDao customerPreferenceDao) {
        this.movieCatalog = movieCatalog;
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
}
public class MovieRecommender {

    @Autowired
    @Qualifier("main")
    private MovieCatalog movieCatalog;

    // ...
}

@Resource

与 @Autowired不同的是 @Resource注解默认是 byName 而 @Autowired 默认是 ByType

例子如下

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Resource(name="myMovieFinder") 
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}

如果未指定name的值 将默认使用方法的bean属性名 或者 字段名称 作为name

如下 name属性值为 movieFinder

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Resource
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}

@Value

@Value通常用于注入外部属性:

@Component
public class MovieRecommender {

    private final String catalog;

    public MovieRecommender(@Value("${catalog.name}") String catalog) {
        this.catalog = catalog;
    }
}

创建一个配置类,使用@PropertySource注解引入外部资源 application.properties

@Configuration
@PropertySource("classpath:application.properties")
public class AppConfig { }

创建一个配置文件application.properties

catalog.name=MovieCatalog

在这种情况下,catalog 参数的值将等于 MovieCatalog

Spring提供的内置转换器支持允许自动处理简单的类型转换 例如 多个逗号分隔的值可以自动转换为数组

如果想自定义类型转换器 可以往容器中添加 ConversionService接口实现类 对象

@Configuration
public class AppConfig {

    @Bean
    public ConversionService conversionService() {
        DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
        conversionService.addConverter(new MyCustomConverter());
        return conversionService;
    }
}
public interface ConversionService {

	// 判断能否进行类型转换
	boolean canConvert(@Nullable Class<?> sourceType, Class<?> targetType);
	boolean canConvert(@Nullable TypeDescriptor sourceType, TypeDescriptor targetType);
    
	// 类型转换,获取合适的转换器进行类型的转换,默认是DefaultConversionService,也可以是自定义的
	@Nullable
	<T> T convert(@Nullable Object source, Class<T> targetType);
	@Nullable
	Object convert(@Nullable Object source, @Nullable TypeDescriptor sourceType, TypeDescriptor targetType);

}

@PostConstruct@PreDestroy

可以使用上述两个注解来回调bean的声明周期中的实例化后方法 @PostConstruct 和 销毁前方法 @PreDestroy

public class CachingMovieLister {

    @PostConstruct
    public void populateMovieCache() {
        // populates the movie cache upon initialization...
    }

    @PreDestroy
    public void clearMovieCache() {
        // clears the movie cache upon destruction...
    }
}

版权声明:程序员胖胖胖虎阿 发表于 2022年9月22日 上午3:24。
转载请注明:[ Spring核心 ] IOC技术 上篇 吃透 Spring | 胖虎的工具箱-编程导航

相关文章

暂无评论

暂无评论...