博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Spring 基础框架一:深入理解Spring IOC
阅读量:3507 次
发布时间:2019-05-20

本文共 9282 字,大约阅读时间需要 30 分钟。

什么是Spring框架

Spring是一种轻量级开发框架,旨在简化开发以及系统的可维护性。

:1、非侵入式设计,可以使应用程序代码对框架的依赖最小化。2、方便解耦、简化开发,将所有对象的创建和依赖关系的维护工作都交给Spring容器的管理。3、支持AOP,提高了程序的复用性。4、支持声明式事务处理,只需要通过配置就可以完成对事物的管理。5、方便程序的测试,Spring提供了对Junit4的支持,可以通过注解方便的测试Spring程序。6、方便集成各种优秀框架。

Spring是很多模块的集合,包括核心容器、数据访问/集成、Web、AOP(面向切面编程)、工具、消息和测试模块。

核心容器

Spring的核心容器是其他模块建立的基础,有spring-core、spring-beans、spring-context等模块组成。

spring-core 模块:提供了框架的基本组成部分,包括控制反转(Inversion of Control,IOC)和依赖注入(Dependency Injection,DI)功能。

spring-beans 模块:主要用来管理bean,这个模块实现BeanFactory的工厂模式,Spring中Bean形式是普通Java类.

Spring Context 模块:此模块表示Spring应用的环境,通过此模块可访问任意Bean,ApplicationContext接口是模块的关键组成.

Spring表达式语言(SpEL):这个模块提供对表达式语言(SpEL)支持

AOP

spring-aop 模块:是 Spring 的另一个核心模块,提供对面向切面编程的支持。

Aspects 模块: 提供与AspectJ集成,AspectJ是另一个面向切面编程的框架。

数据访问与集成

spring-jdbc 模块:对Java JDBC接口再次包装,让Spring应用中使用JDBC更简单。

spring-orm 模块:ORM代表对象关系映射,该模块提供对ORM的支持。Hibernate

spring-oxm 模块:OXM代表对象XML映射器,将 java 对象映射成 XML 数据, 或者将 XML 数据映射成 java 对象,该模块提供对OXM的支持

spring-jms模块(Java Messaging Service):指Java消息传递服务,包含用于生产和使用消息的功能。

Transations 模块:事务模块,该模块提供数据库事务的支持。

Web

spring-web 模块:提供了基本的Web开发集成功能,如文件下载、rest接口支持等。

servlet 模块:提供MVC(Model-View-Controller)功能实现

WebSocket 模块:提供Socket通信,web端的的推送功能;

portlet 模块:实现web模块功能的聚合,(如网站首页(Port)下面可能会有不同的子窗口(Portlet))。

Test

spring-test 模块:主要为测试提供支持的,支持使用JUnit或TestNG对Spring组件进行单元测试和集成测试。

Spring核心思想:IOC/DI、AOP

1、IOC/DI是什么

传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建对象和设置依赖关系。问题:导致类与类之间高耦合,难于测试。而IOC理念,将对象的创建和设置依赖关系交由Spring容器来进行管理,开发人员只需要关注具体实现就可以了。

Spring框架中控制反转(Inversion of Control / IoC)与依赖注入(Dependency Injection / DI)实际上讲的是同一个事情,只是角度不同。

DI依赖注入:依赖类不由程序员实例化,而是通过spring容器帮我们创建指定实例并且将实例动态的注入到需要该对象的类中。

2、Spring IOC 原理

Spring 通过一个配置文件描述 Bean 及 Bean 之间的依赖关系,利用 Java 语言的反射功能实例化Bean 并建立 Bean 之间的依赖关系。Java 1.3之后一个重要特征是反射(reflection),它允许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性,spring就是通过反射来实现注入的。

é¿éé¢è¯ï¼è¯´ä¸springçåçæ¯ä»ä¹ï¼ï¼éspringé¢è¯ä¸é¢ï¼

Spring 启动时读取应用程序提供的 Bean 配置信息,并在 Spring 容器中生成一份相应的 Bean 配置注册表,然后根据这张注册表实例化 Bean,装配好 Bean 之间的依赖关系,为上层应用提供准备就绪的运行环境。其中 Bean 缓存池为 HashMap 实现。

3、Spring 框架中的依赖注入方式

(1)Spring框架依赖注入可以通过XML配置文件和注解的方式进行。

①XML文件注入

示例Demo:创建一个玩家类,该玩家可以使用武器有刀剑和枪。

传统写法:玩家的武器只能是剑Sword,而不能把Sword替换成枪Gun。想替换涉及代码修改。

class Player{      Weapon weapon;      Player(){          // 与 Sword类紧密耦合        this.weapon = new Sword();      }      public void attack() {        weapon.attack();    }}

 依赖注入:将对象的创建和依赖关系的设置交由Spring容器进行管理。消除类之间依赖关系。A类要依赖B类,A类不再直接创建B类,而是把这种依赖关系配置在外部xml文件(或java config文件)中,然后由Spring容器根据配置信息创建、管理bean类。

class Player{      Weapon weapon;      // weapon 被注入进来    Player(Weapon weapon){          this.weapon = weapon;      }      public void attack() {        weapon.attack();    }    public void setWeapon(Weapon weapon){          this.weapon = weapon;      }  }

Weapon类的实例并不在代码中创建,而是外部通过构造函数传入,传入类型是父类Weapon,所以传入的对象类型可以是任何Weapon子类。Spring容器根据配置信息创建所需子类实例,并注入Player类中,如下所示:

 上面代码中<construct-arg ref="weapon"/> ref指向id="weapon"的bean,传入的武器类型是Gun,如果想改为Sword,可以作如下修改:

② 注解方式注入:Bean也可以通过Java注解的方式配置。Java注解直接加在需要装配的Bean Java类上。

要定义一个Bean,可以通过@Bean注解,Spring容器会注册这个Bean,并将方法名作为Bean ID。

示例:SpringConfig.java

@Configurationpublic class SpringConfig {  // 定义 App Bean  @Bean  public App app() {    return new App(logger()); // 调用Bean方法logger()注入Logger Bean实例  }  // 定义 Database Bean  @Bean  public Database database() {   return new Database();  }  // 定义 Logger Bean  @Bean  public Logger logger() {    return new Logger();  }}

可以使用AnnotationConfigApplicationContext读取配置类。

public class Test { public static void main(String[] args) {  // 使用`AnnotationConfigApplicationContext`读取配置类  ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);  App app = context.getBean("app", App.class);}}

(2)Spring 框架中的常见依赖注入方式:

①xml常见注入方式:Setter方法注入、构造函数注入以及工厂方法注入

Setter方法注入:要求 Bean 提供一个默认的构造函数,并为需要注入的属性提供对应的 Setter 方法 。Spring 先调用 Bean 的默认构造函数实例化 Bean 对象,然后通过反射来调用 Setter 方法注入属性值 。 下面我们举一个例子:

public class Book {    /**     * 书名     */    private String name;    /**     * 定价     */    private double price;    /**     * 作者     */    private Author author;    public void setName(String name) {        this.name = name;    }   ...省略get、set}
面纱
25.5
80000

构造函数注入:使用构造函数注入的前提是 Bean 必须提供带参的构造函数。构造函数注入保证一些必要的属性在 Bean 实例化时就得到设置,这样 Bean 在实例化后就可以使用啦。我们为 “书” 这个类,添加一个带参的构造函数:

public Book(String name, double price) {    this.name = name;    this.price = price;}public Book(String name, Author author) {    this.name = name;    this.author = author;}

配置1:<constructor-arg> 的元素中有一个 type 属性,它表示构造函数中参数的类型,这是 spring 用来判断配置项与构造函数入参对应关系的 “桥梁”。

人生的枷锁
35

配置2:如果 Book 类定义的构造函数具有多个类型相同入参,那么就需要依赖配置顺序咯,如果想定义的更加灵活,那么就可以使用 “ 按索引匹配入参”。注意: Spring 底层是采用 Java 反射能力来实现依赖注入的,但 Java 反射无法获知构造函数的参数名,所以只能通过入参类型与索引信息来间接地确定构造函数的配置项与入参之间的对应关系。

人生的枷锁
上海译文出版社

 配置3:如果 Bean 构造函数的入参类型不是基础数据类型,而且入参类型各异,那么可以通过 Java 反射机制获取构造函数的入参类型。

人生的枷锁

 工厂方法注入:工厂类负责创建目标类实例,它对外屏蔽了目标类的实例化细节,返回的类型一般是接口或抽象类的形式。

public class BookFactory {    public Book create() {        return new Book("面纱", "重庆出版社");    }}

配置1:非静态 

配置2:静态

②基于注解的注入

注解方式注册bean,注入依赖 ,主要有四种注解可以注册bean,每种注解可以任意使用,只是语义上有所差异:

  1. @Component:可以用于注册所有bean
  2. @Repository:主要用于注册dao层的bean
  3. @Controller:主要用于注册控制层的bean
  4. @Service:主要用于注册服务层的bean

描述依赖关系主要有两种:

  • @Resource:java的注解,默认以byName的方式去匹配与属性名相同的bean的id,如果没有找到就会以byType的方式查找,如果byType查找到多个的话,使用@Qualifier注解(spring注解)指定某个具体名称的bean。
  • @Resource@Qualifier("userDaoMyBatis")private IUserDao userDao;public UserService(){
  • @Autowired:spring注解,默认是以byType的方式去匹配与属性名相同的bean的id,如果没有找到,就通过byName的方式去查找
  • @Autowired@Qualifier("userDaoJdbc")private IUserDao userDao;

注1:懒加载,即当对象需要用到的时候再去加载。初始化时不进行加载。

注解方式:@Lazy

xml配置方式:

 注2:集合类型属性的注入

张三
李四
王五
aaa
bbb
xxx

4、Bean 作用域

Spring定义了多种Bean作用域,可以基于这些作用域创建bean,包括:

  • 单例(Singleton):在整个应用中,只创建bean的一个实例。
  • 原型(Prototype):每次注入或者通过Spring应用上下文获取的时候,都会创建一个新的bean实例。
  • 会话(Session):在Web应用中,为每个会话创建一个bean实例。
  • 请求(Rquest):在Web应用中,为每个请求创建一个bean实例。

分为注解和xml两种形式:在默认情况下,Spring应用上下文中所有bean都是作为以单例(singleton)的形式创建的。

@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)public class MyIsBean{...}

 

 5、Spring Bean 生命周期【重点】

Bean 的生命周期概括起来就是 4 个阶段

  1. 实例化(Instantiation)
  2. 属性赋值(Populate)
  3. 初始化(Initialization)
  4. 销毁(Destruction)

é¢è¯å®ï¼è¯·ä½ æè¿°ä¸ Spring Bean ççå½å¨æï¼

如上图所示,Bean 的生命周期还是比较复杂的,下面来对上图每一个步骤做文字描述:

  1. Spring启动,查找并加载需要被Spring管理的bean,进行Bean的实例化

  2. Bean实例化后对将Bean的引入和值注入到Bean的属性中

  3. 检查Aware的相关接口并设置相关依赖:①如果Bean实现了BeanNameAware接口的话,Spring将Bean的Id传递给setBeanName()方法  ②如果Bean实现了BeanFactoryAware接口的话,Spring将调用setBeanFactory()方法,将BeanFactory容器实例传入    ③如果Bean实现了ApplicationContextAware接口的话,Spring将调用Bean的setApplicationContext()方法,将bean所在应用上下文引用传入进来。

  4. 如果Bean实现了BeanPostProcessor接口,Spring就将调用他们的postProcessBeforeInitialization()方法。

  5. 如果Bean 实现了InitializingBean接口,Spring将调用他们的afterPropertiesSet()方法。类似的,如果bean使用init-method声明了初始化方法,该方法也会被调用

  6. 如果Bean 实现了BeanPostProcessor接口,Spring就将调用他们的postProcessAfterInitialization()方法。

  7. 注册Destruction相关回调接口,不是真正意义上的销毁(还没使用呢),而是先在使用前注册了销毁的相关调用接口,为了后面第9步真正销毁 bean 时再执行相应的方法。

  8. 此时,Bean已经准备就绪,可以被应用程序使用了。他们将一直驻留在应用上下文中,直到应用上下文被销毁。

  9. 如果bean实现了DisposableBean接口,Spring将调用它的destory()接口方法,同样,如果bean使用了destory-method 声明销毁方法,该方法也会被调用。

下面我们结合代码来直观的看下,在 doCreateBean() 方法中能看到依次执行了这 4 个阶段: 

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)    throws BeanCreationException {    // 1. 实例化    BeanWrapper instanceWrapper = null;    if (instanceWrapper == null) {        instanceWrapper = createBeanInstance(beanName, mbd, args);    }        Object exposedObject = bean;    try {        // 2. 属性赋值        populateBean(beanName, mbd, instanceWrapper);        // 3. 初始化        exposedObject = initializeBean(beanName, exposedObject, mbd);    }    // 4. 销毁-注册回调接口    try {        registerDisposableBeanIfNecessary(beanName, bean, mbd);    }    return exposedObject;}

Spring Aware系列接口:主要用于辅助Spring bean获取Spring容器中的资源

容器管理的 Bean 一般不需要了解容器的状态和直接使用容器, 但是在某些情况下, 是需要在 Bean 中直接对IOC容器进行操作的, 可以通过特定的 Aware 接口来完成。

Spring中提供了一些以Aware结尾的接口,实现了Aware接口的bean再被初始化之后,可以获取相应资源,通过Aware接口,可以对Spring相应资源进行操作。

接口名 描述
ApplicationContextAware 实现了这个接口的类都可以获取到一个 ApplicationContext 对象. 可以获取容器中的所有 Bean
BeanClassLoaderAware 获取 bean 的类加载器
BeanFactoryAware 获取 bean 的工厂
BeanNameAware 获取 bean 在容器中的名字

BeanPostProcessor:解决同一个接口有多个实现类问题

BeanPostProcessor也称为Bean后置处理器,它是Spring中定义的接口,在Spring容器的创建过程中(具体为Bean初始化前后)会回调BeanPostProcessor中定义的两个方法。BeanPostProcessor的源码如下:

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

其中postProcessBeforeInitialization方法会在每一个bean对象的初始化方法调用之前回调;

postProcessAfterInitialization方法会在每个bean对象的初始化方法调用之后被回调。

 

文章参考:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

转载地址:http://pfamj.baihongyu.com/

你可能感兴趣的文章
Java中时间戳和时间格式的转换
查看>>
Dubbo基础知识整理
查看>>
计算机网络知识整理
查看>>
Java基础知识
查看>>
操作系统知识整理
查看>>
实现自己的权限管理系统(二):环境配置以及遇到的坑
查看>>
实现自己的权限管理系统(四): 异常处理
查看>>
实现自己的权限管理系统(十):角色模块
查看>>
实现自己的权限管理系统(十二):权限操作记录
查看>>
实现自己的权限管理系统(十三):redis做缓存
查看>>
实现自己的权限管理系统(十四):工具类
查看>>
JavaWeb面经(一):2019.9.14
查看>>
JavaWeb面经(二):2019.9.16 Synchronized关键字底层原理及作用
查看>>
JavaWeb面试经:redis
查看>>
牛客的AI模拟面试(1)
查看>>
深入浅出MyBatis:MyBatis解析和运行原理
查看>>
Mybatis与Ibatis
查看>>
字节码文件(Class文件)
查看>>
java中的IO流(一)----概述
查看>>
StringBuilder
查看>>