• Spring 学习笔记 IoC 基础


    Spring IoC


    Ioc 是什么

    IoC —— Inversion of Control(控制反转)什么是控制?什么是反转?

    控制反转了什么?
    在很早之前写项目不用 Spring 的时候,都是在 Class 中成员属性中 new 对象的方式。是我们主动去创建对象也就是我们控制了对象的创建。

    public class ServiceImpl implements Service {
        private Dao dao = new DaoImpl();
    }
    

    而 Spring IoC 会主动去创建对象不需要我们主动去创建,也就是 IoC 控制了对象的创建。

    public class ServiceImpl implements Service {
        @Autowired
        private Dao dao;
    }
    

    只需要加上 @Autowired 注解就能自动帮我们注入需要依赖的对象,容器帮助我们查找和注入对应的对象也就是反转。

    依赖注入

    IoC 也称依赖注入(dependency injection, DI) DI 是一个实例化过程,在这个过程中对象需要依赖的类也称组件之间的依赖关系,由容器在创建 bean 时注入这些依赖项。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。IoC 和 DI 它们是一个概念不同维度的描述。

    IoC 基础

    org.springframework.beans 和 org.springframework.context 包是 Spring 框架的基础,BeanFactory 接口提供了能够管理任何类型对象的配置机制。ApplicationContext 是 BeanFactory 的扩展实现。

    • 更容易与 Spring 的 AOP 特性集成
    • 消息资源处理(用于国际化)
    • 事件发布
    • 应用程序层特定的上下文,如 web 应用程序中使用的 WebApplicationContext

    ApplicationContext 表示 IoC 容器,负责实例化、配置和组装 bean。容器通过读取配置文件元数据获取要实例化、配置和组装哪些对象。配置可以通过:xml、java 注解或 java 代码表示。

    Spring 提供了 ApplicationContext 接口的实现,通常创建使用 ClassPathXmlApplicationContext 或 FileSystemXmlApplicationContext 或 AnnotationConfigApplicationContext 的实例。系统与容器相结合这样在容器初始化完毕后,就可以使用 IoC 容器了。
    在这里插入图片描述

    IoC 配置

    在 Spring 中配置 bean 有常用的两种方式:

    1. 基于 xml 的形式:基于 xml 配置将这些 bean 配置为 顶级元素为
    2. 基于 java 注解的形式:基于注解将使用 @Configuration 中配置 @Bean

    示例:以下显示了声明了一个 Person 的 bean

    public class Person {
    	private String name;
    
    	public String getName() {
    		return name;
    	}
    
    	public void setName(String name) {
    		this.name = name;
    	}
    }
    
    <?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 http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <!--
        id 属性是标识单个 bean 定义且是唯一值不可重复
        class 属性是定义 bean 的类型并使用完整的路径表示
        -->
        <bean id="person" class="com.feil.springtest.ioc.test01.Person">
            <property name="name" value="123"/>
        </bean>
    </beans>
    

    实例化容器

    public static void main(String[] args) {
    		ApplicationContext context = new ClassPathXmlApplicationContext("classpath:conf/spring/application-ioc.xml");
    		Person person = (Person) context.getBean("person");
    		System.out.println(person);
    		System.out.println(person.getName());
    	}
    

    运行结果

    com.feil.springtest.ioc.test01.Person@ff5b51f
    123
    
    Process finished with exit code 0
    

    Bean 概述

    Spring IoC 容器管理一个或多个 bean。这些 bean 是使用您提供给容器的配置元数据创建的比如 xml 配置形式。
    在容器内部这些配置元数据被封装在 BeanDefinition 对象内,一个 BeanDefinition 对象就相当于一个 标签的内容。

    Bean 命名
    每个 bean 都有一个或多个标识。bean 通常只有一个标识符 id。基于 xml 的配置元数据中,可以使用 id 属性、name 属性或两者都使用来指定 bean 标识符。id 属性允许您只指定一个 id 通常都是类名首字母小写声明。如果还希望 bean 使用其他别名可以在 name 属性中指定他们,以逗号、分号或空格分隔。

    实例化 bean
    使用基于 xml 的配置元数据,则指定要在元素的 class 属性中实例化的对象的类型(或类)。这个类属性(在内部是 BeanDefinition 实例上的一个类属性)。通过这个 class 属性通过反射调用其构造函数直接创建 bean。Spring 一般都是通过这样的形式来创建 bean。

    还有其他方式实例化一个 bean:
    使用静态工厂方法来实例化一个 bean

    public class ClientService {
        private ClientService() {}
    
        public static ClientService createInstance() {
            return new ClientService();
        }
    }
    
    <bean id="clientService" class="examples.ClientService" factory-method="createInstance"/>
    

    使用实例方法实例化一个 bean

    public class DefaultServiceLocator {
        public ClientService createClientServiceInstance() {
            return new ClientServiceImpl();
        }
    }
    
    <bean id="serviceLocator" class="examples.DefaultServiceLocator"></bean>
    
    <bean id="clientService" factory-bean="serviceLocator" factory-method="createClientServiceInstance"/>
    

    一个工厂类可以声明很多 createInstance 方法,只需要在 xml 配置就可以获取多个不同的 bean 实例了。在 Spring 的文档中 “factory bean” 是在 Spring 容器中配置的 bean,它通过静态或实例工厂方法来创建对象。相反 FactoryBean 是特定于 Spring 中 FactoryBean 接口的实现。

    依赖
    依赖可以理解为几个对象一起通过写作来实现目标功能。容器在创建 bean 时注入这些依赖项,使用依赖注入代码更加简洁,当 bean 具有其他依赖 bean 关系时,不需要主动去查找对应的 bean 实例,也不知道对应 bean 具体的实现到底是什么,特别是依赖关系是接口或者抽象类时实现了解耦的功能。

    构造函数依赖注入
    基于构造器的依赖注入是容器来选择调用一个构造函数来完成的,构造函数有若干个参数,每个参数代表一个依赖项。下面例子显示了一个只能通过构造函数注入的类。

    public class SimpleMovieLister {
    
        private MovieFinder movieFinder;
    
        public SimpleMovieLister(MovieFinder movieFinder) {
            this.movieFinder = movieFinder;
        }
    }
    
    <beans>
        <bean id="simpleMovieLister" class="com.xxx.xxx.SimpleMovieLister">
            <constructor-arg ref="movieFinder"/>
        </bean>
    
        <bean id="movieFinder" class="com.xxx.xxx.MovieFinder"/>
    </beans>
    

    基于 setter 的依赖注入
    基于 setter 的依赖注入是通过容器调用 bean 里的 setter 方法来实现的。下面例子显示了一个只能通过使用纯 setter 注入注入依赖关系的类。

    public class SimpleMovieLister {
    
        private MovieFinder movieFinder;
    
        public void setMovieFinder(MovieFinder movieFinder) {
            this.movieFinder = movieFinder;
        }
    }
    
    <beans>
        <bean id="simpleMovieLister" class="com.xxx.xxx.SimpleMovieLister">
            <property name="movieFinder" ref="movieFinder"/>
        </bean>
    
        <bean id="movieFinder" class="com.xxx.xxx.MovieFinder"/>
    </beans>
    

    注入集合类型

    <bean id="moreComplexObject" class="example.ComplexObject">
        <!-- java.util.Properties -->
        <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>
        
        <!-- java.util.List -->
        <property name="someList">
            <list>
                <value>a list element followed by a reference</value>
                <ref bean="myDataSource" />
            </list>
        </property>
        
        <!-- java.util.Map -->
        <property name="someMap">
            <map>
                <entry key="an entry" value="just some string"/>
                <entry key ="a ref" value-ref="myDataSource"/>
            </map>
        </property>
        
        <!-- java.util.Set -->
        <property name="someSet">
            <set>
                <value>just some string</value>
                <ref bean="myDataSource" />
            </set>
        </property>
    </bean>
    

    懒加载
    默认情况下 Spring IoC 容器会在不管是否使用到 bean 都会提前创建好。这样的好处是配置或周围环境中的错误会立即被发现,而不是几小时甚至几天之后才发现。当不需要提前加载可以通过 bean 标签中的 lazy-init 属性来延迟初始化,延迟初始化的 bean 告诉 IoC 容器在第一次请求时创建bean 实例,而不是在启动时。

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

    还可以全局的设置所有 bean 都延迟初始化

    <beans default-lazy-init="true">
        <!-- no beans will be pre-instantiated... -->
    </beans>
    

    自动装配
    Spring IoC 容器可以自动装配 bean 之间的关系。自动装配可以显著减少指定属性或构造函数参数的需要。

    使用基于 xml 的配置元数据,可以使用元素的 autowire 属性为 bean 定义指定 autowire 模式。自动装配功能有四种模式。

    模式 说明
    no 默认没有自动装配,Bean 引用必须由 ref 元素定义
    byName 通过属性名,Spring 寻找和属性名相同的 bean
    byType 通过属性类型,Spring 寻找和依赖项匹配的类型 bean,如果存在多个则会异常
    constructor 类似于 byType,但适用于构造函数参数。如果容器中没有构造函数参数类型的bean,则会异常

    autowire no

    <bean id="simpleMovieLister" class="com.xxx.xxx.SimpleMovieLister">
        <property name="movieFinder" ref="movieFinder"/>
    </bean>
    
    <bean id="movieFinder" class="com.xxx.xxx.MovieFinder"/>
    

    autowire byName
    当一个 bean 节点带有 byName 属性时:

    1. 将查找该类中的所有 set 方法并将 set 去掉首字母小写
    2. 去 Spring IoC 容器中查询时候有此名称 id 的 bean
    3. 如果有则注入,如果没有就抛出异常
    <bean id="simpleMovieLister" class="com.xxx.xxx.SimpleMovieLister" autowire="byName">
          
    <bean id="movieFinder" class="com.xxx.xxx.MovieFinder"/>
    

    autowire byType
    使用 byType 要保证:此依赖的 bean 类型在 Spring IoC 容器中是唯一的。

    <bean id="simpleMovieLister" class="com.xxx.xxx.SimpleMovieLister" autowire="byType">
          
    <bean id="movieFinder" class="com.xxx.xxx.MovieFinder"/>
    

    全局 autowire 配置

    <beans default-autowire="byName">
    </beans>
    

    基于注解自动装配
    首先需要 context:annotation-config/ 开启注解的属性。

    1. @Autowired 按类型自动装配
    2. @Qualifier + @Autowired 按 byName 自动装配 @Qualifier 不能单独使用
    3. @Resource 如果指定了 name 则按属性进行 byName 查找;如果指定了 type 则按 byType 查找;其次进行默认 byName 查找;如果以上都不行则按 byType 方式自动装配,都不成功则报异常

    Bean 范围
    Spring IoC 容器中默认 bean 的实例是单例的全局唯一,但是 Spring IoC 容器中提供了 6 种 bean 的作用域。

    范围 描述
    singleton (默认值)为每个 Spring IoC 容器将单个 bean 定义范围限定为单个对象实例
    prototype 使得每次从容器中获取都是全新的一个 bean 实例
    request 每个 HTTP 请求都有自己的 bean 实例
    session 将 bean 定义的范围限定为 HTTP 会话的生命周期内
    application 将 bean 定义的范围限定为 ServletContext 的生命周期
    websocket 将 bean 定义的范围限定为 WebSocket 的生命周期

    singleton 的范围
    只管理一个单例 bean 所有对 id 或 id 匹配该 bean 的获取请求都会导致 Spring IoC 容器返回该 bean 的特定实例。
    定义一个bean定义,并且它的作用域是一个单例对象时,Spring IoC容器将创建该bean定义定义的对象的一个实例。此单一实例存储在此类单例 bean 的缓存中,对于该命名 bean 的所有后续请求和引用都返回缓存的对象。
    在这里插入图片描述

    prototype 的范围
    bean 声明非单例原型范围会在每次发出对特定 bean 的请求时创建一个新的 bean 实例。也就是说,bean 被注入到另一个 bean 中,或者您通过容器上的 getBean() 方法调用请求它会返回一个新的 bean 实例。
    在这里插入图片描述

    Web 应用中的作用域
    在 Web 范围的 ApplicationContext 时,才可以使用 request、session、Application 和 websocket 范围。

    request 的范围
    为每个 HTTP 请求使用 LoginAction bean 定义来创建 LoginAction bean 的新实例。

    <bean id="loginAction" class="com.something.LoginAction" scope="request"/>
    

    在使用包扫描注解开发时可以使用 @RequestScope 注释将组件分配给请求范围

    @RequestScope
    @Component
    public class LoginAction {
        // ...
    }
    

    session 的范围
    为单个 HTTP session 的生命周期使用 UserPreferences bean 定义来创建 UserPreferences bean的新实例

    <bean id="loginAction" class="com.something.LoginAction" scope="session"/>
    

    在使用包扫描注解开发时可以使用 @SessionScope 注释将组件分配给请求范围

    @SessionScope
    @Component
    public class LoginAction {
        // ...
    }
    

    application 的范围
    为整个 web 应用程序使用一次 LoginAction bean 定义来创建 LoginAction bean 的新实例

    <bean id="loginAction" class="com.something.LoginAction" scope="application"/>
    

    在使用包扫描注解开发时可以使用 @ApplicationScope 注释将组件分配给请求范围

    @ApplicationScope
    @Component
    public class LoginAction {
        // ...
    }
    

    自定义范围 bean
    bean 的范围是可扩展的,可以自定义范围,要将自定义范围集成到 Spring IoC 容器中需要实现 org.springframework.beans.factory.config.Scope 接口,接口中有 5 个方法:

    1. Object get(String name, ObjectFactory objectFactory):从自定义范围内获取对象
    2. Object remove(String name): 从自定义范围内删除对象
    3. void registerDestructionCallback(String name, Runnable destructionCallback):将 Scope 实现注册销毁后的回调方法,如果想要注册销毁相应的对象则由 Spring IoC 容器注册相应的销毁回调
    4. Object resolveContextualObject(String key):用于解析相应的上下文数据,比如 request 作用域将返回 request 中的属性。
    5. String getConversationId():作用域的会话标识,比如 session 作用域将是 sessionId。

    让我们来看一看 thread 线程范围的作用域
    代码实现

    public class SimpleThreadScope implements Scope {
      // 实现
    }
    Scope threadScope = new SimpleThreadScope();
    beanFactory.registerScope("thread", threadScope);
    

    xml 实现

    <?xml version="1.0" encoding="UTF-8"?>
    <beans>
    
        <bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
            <property name="scopes">
                <map>
                    <entry key="thread">
                        <bean class="org.springframework.context.support.SimpleThreadScope"/>
                    </entry>
                </map>
            </property>
        </bean>
    
        <bean id="thing2" class="x.y.Thing2" scope="thread">
        </bean>
    
        <bean id="thing1" class="x.y.Thing1">
        </bean>
    </beans>
    

    自定义 bean 的性质
    Spring 框架提供了许多接口,您可以使用它们定制 bean 的性质,有如下几个类型的接口

    1. 生命周期回调
    2. ApplicationContextAware 和 BeanNameAware
    3. 其他 Aware 接口

    生命周期回调
    要想与 Spring IoC 容器 bean 生命周期管理进行交互可以实现 InitializingBean 和 DisposableBean 接口容器会主动调用 afterPropertiesSet()、destroy() 让 bean 在初始化和销毁 bean 时执行某些操作。
    JSR-250 @PostConstruct 和 @PreDestroy 注解通常被认为是 Spring 应用程序中接收生命周期回调的最佳实践。使用这些注解意味着您的 bean 没有耦合到特定于 spring 的接口。
    如果不希望使用 JSR-250 注解,但仍然希望降低耦合,请考虑 init-method 和 destroy-method bean 定义 xml 元数据。

    使用不同的初始化方法为同一个 bean 配置多个生命周期机制,调用顺序如下

    1. 用注解 @PostConstruct
    2. InitializingBean 接口的 afterPropertiesSet() 方法
    3. 自定义配置的init()方法

    销毁方法调用顺序

    1. 用注解 @PreDestroy
    2. DisposableBean 接口的 destroy() 方法
    3. 自定义配置的 destroy() 方法

    ApplicationContextAware 和 BeanNameAware
    当 Spring IoC 容器创建 bean 发现 bean 实现了 org.springframework.context.ApplicationContextAware 时将为该 bean 提供 ApplicationContext 的引用

    public interface ApplicationContextAware {
        void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
    }
    

    当 Spring IoC 容器创建 bean 发现 bean 实现了org.springframework.beans.factory.BeanNameAware 时将为该 bean 提供 ApplicationContext 的引用将为该 bean 提供当前的 beanName

    public interface BeanNameAware {
        void setBeanName(String name) throws BeansException;
    }
    

    其他 Aware 接口

    1. ApplicationContextAware
    2. ApplicationEventPublisherAware
    3. BeanClassLoaderAware
    4. BeanFactoryAware
    5. BeanNameAware
    6. BootstrapContextAware
    7. LoadTimeWeaverAware
    8. MessageSourceAware
    9. NotificationPublisherAware
    10. ResourceLoaderAware
    11. ServletConfigAware
    12. ServletContextAware

    Bean 扩展
    BeanPostProcessor

    public interface BeanPostProcessor {
        @Nullable
        default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
            return bean;
        }
    
        @Nullable
        default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            return bean;
        }
    }
    
    

    BeanPostProcessor 接口定义了回调方法,您可以实现这些方法来提供您自己的实例化逻辑、依赖项解析逻辑等等。您可以配置多个 BeanPostProcessor 实例,并且可以通过设置 order 属性来控制这些 BeanPostProcessor 实例的执行顺序。

    1. BeanPostProcessor 的实例可以操作 bean 实例。也就是说,Spring IoC 容器实例化一个 bean 实例,然后 BeanPostProcessor 实例执行它们的工作。
    2. BeanPostProcessor 实例的作用域是每个 Spring IoC 容器。
      后置处理器可以对 bean 实例执行任何操作通常检查回调接口,或者使用代理包装 bean。

    如何定义一个 BeanPostProcessor

    public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {
    
        public Object postProcessBeforeInitialization(Object bean, String beanName) {
            return bean;
        }
    
        public Object postProcessAfterInitialization(Object bean, String beanName) {
            System.out.println("Bean '" + beanName + "' created : " + bean.toString());
            return bean;
        }
    }
    
    <bean class="scripting.InstantiationTracingBeanPostProcessor"/>
    

    测试

    public static void main(final String[] args) throws Exception {
            ApplicationContext ctx = new ClassPathXmlApplicationContext("scripting/beans.xml");
            Messenger messenger = (Messenger) ctx.getBean("messenger");
            System.out.println(messenger);
        }
    

    BeanFactoryPostProcessor

    public interface BeanFactoryPostProcessor {
        void postProcessBeanFactory(ConfigurableListableBeanFactory var1) throws BeansException;
    }
    

    接口的作用类似于 BeanPostProcessor,但有一个主要区别:BeanFactoryPostProcessor 操作bean 配置元数据。也就是说,Spring IoC 容器允许 BeanFactoryPostProcessor 读取配置元数据,并可能在容器实例化除 BeanFactoryPostProcessor 实例之外的任何 bean 之前更改它。此外,BeanFactoryPostProcessor 实例的范围是 Application 的范围

    示例:类名替换 PropertyPlaceholderConfigurer
    可以使用 PropertyPlaceholderConfigurer 使 bean 属性值获取从 bean 定义外部化到一个单独的文件中,比如 properties。

    <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="locations" value="classpath:com/something/jdbc.properties"/>
    </bean>
    
    <bean id="dataSource" destroy-method="close"
            class="org.apache.commons.dbcp.BasicDataSource">
        <property name="driverClassName" value="${jdbc.driverClassName}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
    

    显示了从外部属性文件配置的属性。在运行时,PropertyPlaceholderConfigurer 应用于替换数据源某些属性的元数据。要替换的值指定为表单 ${property-name} 的占位符,它遵循 Ant、log4j和 JSP EL 样式。
    我们来看一看 PropertyPlaceholderConfigurer 实际实现了 BeanFactoryPostProcessor 接口,并重写了 postProcessBeanFactory 方法来实现。

    /**
    	 * {@linkplain #mergeProperties Merge}, {@linkplain #convertProperties convert} and
    	 * {@linkplain #processProperties process} properties against the given bean factory.
    	 * @throws BeanInitializationException if any properties cannot be loaded
    	 */
    	@Override
    	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    		try {
    			Properties mergedProps = mergeProperties();
    
    			// Convert the merged properties, if necessary.
    			convertProperties(mergedProps);
    
    			// Let the subclass process the properties.
    			processProperties(beanFactory, mergedProps);
    		}
    		catch (IOException ex) {
    			throw new BeanInitializationException("Could not load properties", ex);
    		}
    	}
    

    还可以使用 PropertyPlaceholderConfigurer 替换类名称,这在您必须在运行时选择特定实现类时有时很有用

    <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="locations">
            <value>classpath:com/something/strategy.properties</value>
        </property>
        <property name="properties">
            <value>custom.strategy.class=com.something.DefaultStrategy</value>
        </property>
    </bean>
    
    <bean id="serviceStrategy" class="${custom.strategy.class}"/>
    

    使用 FactoryBean
    FactoryBean 从名字来看以 bean 结尾那应该就是一个 bean 吧,没错它确实是一个 bean,不同于普通 Bean 的是:它是实现了 FactoryBean 接口的 Bean,根据该 Bean 的 ID 从BeanFactory 中获取的实际上是 FactoryBean 的 getObject() 返回的对象,而不是FactoryBean 本身,如果要获取 FactoryBean 对象,请在 id 前面加一个 & 符号来获取。
    FactoryBean 接口提供了三种方法:

    1. Object getObject():返回工厂创建的对象的实例。实例可以共享,这取决于该工厂返回的是单例还是原型
    2. isSingleton():factoryBean 创建的实例是否是单实例
    3. getObjectType():返回 getObject() 方法返回的对象类型

    以上就是重新学习 Spring IoC 的笔记,到最后发现写了好长一篇,帮助了自己巩固记忆,如果写的不足之处望指出谢谢各位观看!

  • 相关阅读:
    C# 文件操作
    Wpf ListView展示风格
    PowerShell->>获取本地计算机的用户组和组成员
    MySQL->>innodb_autoinc_lock_mode参数控制auto_increment 插入数据时相关锁的模式
    SQL Server ->> 使用CROSS APPLY语句是遇到聚合函数中包含外部引用列时报错
    【转】Kettle发送邮件步骤遇到附件名是中文名变成乱码的问题解决办法
    SSIS ->> Excel Destination无法接受大于255个字符长度的字符字段
    SQL Server ->> 存储过程sp_describe_first_result_set解析T-SQL语句的结果集结构信息
    Windows ->> 解决Windows 10下面无法多用户同时远程桌面
    SQL Server ->> 查询添加XLOCK表提示不阻塞其他线程
  • 原文地址:https://www.cnblogs.com/liufeichn/p/11961617.html
Copyright © 2020-2023  润新知