Spring实战与源码解析之SpringIOC应用<一>

11 篇文章 1 订阅
6 篇文章 1 订阅

SpringIOC

    • 本系列的spring讲集,主要是由浅至深,从应用到源码解析去一层一层的剥开spring的神秘面纱,创作不易,喜欢的朋友给个➕关注,有写的不够到位的地方欢迎评论区留言。。。

什么是Spring

    • 面试中面试官经常问到,我们经常会说spring是ioc和aop,包括写这遍文章之前我也是这样回答,但是通过系统的学习后,下次面试中我会说到,spring是一个公司也是一个产品,ioc和aop是spring Framework中众多项目中的其中两个小项目,Spring Framework产品如下图:
    • 原文在这里插入图片描述

什么是 IOC

    • 控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫依赖注入Dependency Injection,简称DI),注意:DI是实现IOC的一种技术手段。另外还有两种实现方式叫“依赖查找”(Dependency Lookup)和依赖拖拽,这两种实际场景比较少用,后续会写到具体实现,但是还是以依赖注入为重点展开编写。
    • 作用:控制反转,表示对象的控制权本来是在程序员手中的,现在交给了Spring,所以Spring获得了对象的控制权,比如,Spring负责去进行实例化得到对象,Spring负责通过反射去给对象中的属性进行赋值,这些动作都不需要程序员去做,Spring自动帮程序员做了。
    • 如下图:在这里插入图片描述

为什么要使用SpringIOC

      • 在日常程序开发过程中,我们推荐面向抽象编程,面向抽象编程会产生类的依赖,当自身足够强大也可以自己写一个管理的容器,但是既然spring已经实现了,并且spring如此优秀,我们只需要学习spring,站在巨人的肩膀上去成长即可。
      • 当我们有了一个管理对象的容器之后,类的产生过程也交给了容器,至于我们自己的app则不用去关心这些对象的产生(spring产生之初的想法)。

Spring实现IOC的思路和方法

      • spring实现IOC的思路是提供一些配置信息用来描述类之间的依赖关系,然后由容器去解析这些配置信息,继而维护好对象之间的依赖关系,前提是对象之间的依赖关系必须在类中定义好,比如A.class中有一个B.class的属性,那么我们可以理解为A依赖了B。既然我们在类中已经定义了他们之间的依赖关系那么为什么还需要在配置文件中去描述和定义呢?
      1. 应用程序中提供类,提供依赖关系(属性或者构造方法)
      1. 把需要交给容器管理的对象通过配置信息告诉容器(xml、annotation、javaconfig)
      1. 把各个类之间的依赖关系通过配置信息告诉容器
        • 配置这些信息的方法分别是xml、annotation和javaconfig维护的过程称为自动注入,自动注入的方法有两种:构造方法和setter(因为接口注入spring4已经已经不使用了,原因:不够人性化),自动注入的值可以是对象、数组、map、list和常量比如字符串整形等
        • 以上三个描述的方法实现如下图:
        • 1.提供依赖关系
          在这里插入图片描述
        • 2.setter方式注入
          在这里插入图片描述
        • 3.加载xml并根据bean名称获取对象
          在这里插入图片描述
        • 4.构造器注入的方式
          在这里插入图片描述
          在这里插入图片描述
        • Collections注入方式:
        • < tlist/>、<set/>、<map/> 和 <props/> 元素分别设置 Java 集合类型 List、Set、Map 和 Properties 的属性和参数

<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 支持具有命名空间的可扩展配置格式,这些格式基于 XML 模式定义。 本章讨论的 bean 配置格式是在 XML Schema 文档中定义的。 但是,p 命名空间并未在 XSD 文件中定义,仅存在于 Spring 的核心中。
<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 name="classic" class="com.example.ExampleBean">
        <property name="email" value="someone@somewhere.com"/>
    </bean>

    <bean name="p-namespace" class="com.example.ExampleBean"
        p:email="someone@somewhere.com"/>
</beans>
        • 查找方法注入
          查找方法注入是容器覆盖容器管理bean上的方法并返回容器中另一个bean的查找结果的能力。查找通常涉及原型bean,如上一节中描述的场景。Spring框架通过使用来自CGLIB库的字节码来动态生成覆盖该方法的子类来实现此方法注入。
        • 注意一下几点:
        • 1、要使这种动态子类化工作,Spring bean 容器子类化的类不能是 final,要覆盖的方法也不能是 final。
          2、对具有抽象方法的类进行单元测试需要您自己对类进行子类化并提供抽象方法的存根实现
          3、组件扫描也需要具体的方法,这需要具体的类来获取
          4、另一个关键限制是查找方法不适用于工厂方法,尤其不适用于配置类中的 @Bean 方法,因为在这种情况下,容器不负责创建实例,因此无法创建运行时生成的动态子类。

        • 注解方式的实现
public abstract class CommandManager {

    public Object process(Object commandState) {
        Command command = createCommand();
        command.setState(commandState);
        return command.execute();
    }

    @Lookup("myCommand")
    protected abstract Command createCommand();
}
        • 或者,更惯用的是,您可以依靠目标 bean 根据查找方法的声明返回类型进行解析:
public abstract class CommandManager {

    public Object process(Object commandState) {
        Command command = createCommand();
        command.setState(commandState);
        return command.execute();
    }

    @Lookup
    protected abstract Command createCommand();
}

Spring的编程风格

以下三种均可以混合使用,其中springboot,也就是spring5版本后使用的最多就是	Configuration方式最多,在本篇中主要就是大概先过一次基础,后续源码讲解中会详细说到
    1. Schema-based Container Configuration(XML配置) Annotation-based
    1. Container Configuration(注解) Java-based Container
    1. Configuration(@Configuration配置类)

自动装配

  • ioc的注入有两个地方需要提供依赖关系,一是类的定义中,二是在spring的配置中需要去描述。自动装配则把第二个取消了,即我们仅仅需要在类中提供以来,继而把对象交给容器管理即可完成注入。

  • 在实际开发中,描述类之间的依赖关系通常是大篇幅的,如果使用自动装配则省去了很多配置,并且如果对象的依赖发生更新我们可以不需要去更新配置,但是也带来了一定的缺点

    • 优点

        • 自动装配可以显著的减少指定属性或构造函数参数的需要
      • 自动装配可以随着对象的发展更新配置。例如,如果需要向类添加依赖项,则无需修改配置即可自动满足该依赖项。因此,自动装配在开发过程中特别有用,当代码库变得更稳定时,不会否定切换到显式装配到选项
      • 实现自动装配的四种方式
方法描述
no默认)没有自动装配。 Bean 引用必须由 ref 元素定义。 对于较大的部署,不建议更改默认设置,因为明确指定协作者可以提供更好的控制和清晰度。 在某种程度上,它记录了系统的结构。
byName按属性名称自动装配。 Spring 查找与需要自动装配的属性同名的 bean。 例如,如果一个 bean 定义被设置为按名称自动装配并且它包含一个主属性(即它有一个 setMaster(…) 方法),Spring 会查找一个名为 master 的 bean 定义并使用它来设置属性。
byType如果容器中恰好存在一个属性类型的 bean,则让属性自动装配。 如果存在多个,则会引发致命异常,这表明您不能为该 bean 使用 byType 自动装配。 如果没有匹配的 bean,则不会发生任何事情(未设置属性)。
constructor类似于 byType 但适用于构造函数参数。 如果容器中没有一个构造函数参数类型的 bean,则会引发致命错误。
    • 使用最多的是byType

          • 全局装配
            在这里插入图片描述
          • 单独装配
            在这里插入图片描述

@Autowired和@Resource的区别

    • @Resource
      • 1、默认按照byName方式装配,属于javaee自带注解,没有指定name时,name默认时变量名;
      • 2、如果name一致,根据type区分,如果还一样报错;
    • @Autowired
      • 1、默认按byType方式装配,spring注解,默认不允许为null,需要为null时,将required设置成false;
      • 2、当一个接口存在多个实现类时,想要不报错,有一下几种方式:
        A、在实现类上加上@Primary注解,表示优先注入该实现类;
        B、使用@Qualifier指定注入的实例;
        C、 当Autowired按byType查出多个时,会按byName来匹配;name表示变量名;
        两者加载的时候,如果无法注入唯一对象,就抛出异常;

懒加载

    • 默认情况下,ApplicationContext 实现会在初始化过程中急切地创建和配置所有单例 bean。 通常,这种预实例化是可取的,因为可以立即发现配置或周围环境中的错误,而不是在几小时甚至几天之后。 当这种行为不可取时,您可以通过将 bean 定义标记为延迟初始化来防止单例 bean 的预实例化。 一个延迟初始化的 bean 告诉 IoC 容器在它第一次被请求时创建一个 bean 实例,而不是在启动时。
    • 在 XML 中,此行为由 < bean/> 元素上的 lazy-init 属性控制,如以下示例所示:
<bean id="lazy" class="com.something.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.something.AnotherBean"/>

Spring 作用域

    • 这里会在后面SpringMVC篇根据实战来解析几种作用域的作用。。。。

Spring生命周期与回调

Spring Framework提供了许多界面,可用于自定义bean的性质。spring将它们分组如下:

1、生命周期回调

要与容器对bean生命周期的管理交互,您可以实现Spring InitializingBean和DisposableBean接口。容器为前者调用 afterPropertiesSet(),为后者调用destroy(),让bean在初始化和销毁您的bean时执行某些操作。

初始化回调

org.springframework.beans.factory.InitializingBean接口允许bean在容器对bean上设置了所有必要的属性后执行初始化工作。InitializingBean接口指定了一种方法:

void afterPropertiesSet() throws Exception;

但是不建议使用这种方式,因为它将代码与Spring耦合。建议使用@PostConstruct注释或指定POJO初始化方法。对于基于XML的配置元数据

<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>
public class ExampleBean {

    public void init() {
        // do some initialization work
    }
}

前面的例子与以下示例(由两个列表组成)的效果几乎完全相同:

<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements InitializingBean {

    @Override
    public void afterPropertiesSet() {
        // do some initialization work
    }
}

然而,前面两个示例中的第一个没有将代码与Spring耦合。

销毁回调

实现org.springframework.beans.factory.DisposableBean接口,当包含bean的容器被销毁时,bean可以获得回调。这边就不做代码比较的方式,感兴趣的可以看spring官方文档:spring文档地址

<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements DisposableBean {

    @Override
    public void destroy() {
        // do some destruction work (like releasing pooled connections)
    }
}
默认初始化和销毁方法

除了以上使用Spring特定的InitializingBean和DisposableBean回调接口的初始化和销毁方法回调,spring还提供了另外一种init()initialize()、**dispose()**等名称编写方法。理想情况下,此类生命周期回调方法的名称在整个项目中标准化,以便所有开发人员使用相同的方法名称并确保一致性。

    • 可以将Spring容器配置为“查找”命名初始化,并销毁每个bean上的回调方法名称。
    • 假设您的初始化回调方法名为init(),您的销毁回调方法名为destroy()。然后,您的类类似于以下示例中的类:
public class DefaultBlogService implements BlogService {

    private BlogDao blogDao;

    public void setBlogDao(BlogDao blogDao) {
        this.blogDao = blogDao;
    }

    // this is (unsurprisingly) the initialization callback method
    public void init() {
        if (this.blogDao == null) {
            throw new IllegalStateException("The [blogDao] property must be set.");
        }
    }
}

然后,在xml中的可以这样写:

<beans default-init-method="init">

    <bean id="blogService" class="com.something.DefaultBlogService">
        <property name="blogDao" ref="blogDao" />
    </bean>

</beans>

这种写法的描述:顶级<beans/>元素属性上存在default-init-method属性,导致Spring IoC容器将bean类上称为init的方法识别为初始化方法回调。当bean创建和组装时,如果bean类有这样的方法,它会在适当的时候调用它。

结合生命周期机制

可以选择三个选项来控制bean生命周期行为:

    • InitializingBean和DisposableBean回调接口
    • 自定义init()和destroy()方法
    • @PostConstruct和@PreDestroy注释。您可以结合这些机制来控制给定的bean。
2、 ApplicationContextAware和BeanNameAware
    • 当ApplicationContext创建实现org.springframework.context.ApplicationContextAware接口的对象实例时,该实例将引用该ApplicationContext。以下列表显示了ApplicationContextAware接口的定义:
public interface ApplicationContextAware {

    void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}

因此,bean可以通过ApplicationContext接口或通过转换对该接口的已知子类(如暴露其他功能的ConfigurableApplicationContext的引用以编程方式操作创建它们的ApplicationContext。一种用途是编程检索其他豆类。有时这种能力很有用。但是不建议使用这种,因为它将代码与Spring耦合,并且不遵循Control反转样式

因此spring推荐BeanNameAware的方式,以下列表显示了BeanNameAware接口的定义:

public interface BeanNameAware {

    void setBeanName(String name) throws BeansException;
}

回调在正常bean属性的填充后调用,但在初始化回调(如InitializingBean.afterPropertiesSet()或自定义init方法)之前调用。

3、其他Aware接口

除了ApplicationContextAware和BeanNameAware外,Spring还提供了广泛的Aware回调接口,允许bean向容器表明它们需要特定的基础设施依赖性。一般来说,名称指示依赖类型。下表总结了最重要的Aware接口:

名字注入依赖项
ApplicationContextAware声明ApplicationContext。
ApplicationEventPublisherAware封闭ApplicationContext的事件发布者。
BeanClassLoaderAware用于加载bean的类加载程序。
BeanFactoryAware宣布BeanFactory。
BeanNameAware申报bean的名称。
LoadTimeWeaverAware用于在加载时处理类定义的定义编织器。
MessageSourceAware配置消息解决策略(支持参数化和国际化)。
NotificationPublisherAwareSpring JMX通知发布者。
ResourceLoaderAware为低级别访问资源配置了加载程序。
ServletConfigAware当前ServletConfig容器运行。仅在具有网络意识的Spring ApplicationContext有效。
ServletContextAware容器运行的当前ServletContext。仅在具有网络意识的Spring ApplicationContext有效。

再次注意,使用这些接口会将您的代码与Spring API捆绑起来,并且不遵循Control反转样式。因此,我们建议它们用于需要对容器进行编程访问的基础设施bean。

BeanNameGenerator

BeanNameGenerator是beans体系非常重要的一个组件,主要功能是从一定的条件中计算出bean的name.如果出现问题,是可以规避的。同样可以重写解决。

Spring中Bean的生命周期

  1. 解析类得到BeanDefinition
  2. 如果有多个构造方法,则要推断构造方法
  3. 确定好构造方法后,进行实例化得到一个对象
  4. 对对象中的加了@Autowired注解的属性进行属性填充
  5. 回调Aware方法,比如BeanNameAware,BeanFactoryAware
  6. 调用BeanPostProcessor的初始化前的方法
  7. 调用初始化方法
  8. 调用BeanPostProcessor的初始化后的方法,在这里会进行AOP
  9. 如果当前创建的bean是单例的则会把bean放入单例池
  10. 使用bean
  11. Spring容器关闭时调用DisposableBean中destory()方法

这里因为自己没有去尝试自定义,所以应该会留在后面深入学源码的时候会在文章里面尝试自定义并且后续文章里面详细写到,感兴趣的朋友可以自行百度去尝试写一个

以上就是springioc一些平时使用比较多应用的笔记了,创造不易,喜欢的朋友点个➕

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值